PoC Smart Escrows (#5340)

* wasmedge in unittest

* add WashVM.h and cpp

* accountID comparison (vector<u8>) working

* json decode tx and ledger object with two buffers working

* wasm return a buffer working

* add a failure test case to P2P3

* host function return ledger sqn

* instruction gas and host function gas

* basics

* add scaffold

* add amendment check

* working PoC

* get test working

* fix clang-format

* prototype #2

* p2p3

* [WIP] P4

* P5

* add calculateBaseFee

* add FinishFunction preflight checks (+ tests)

* additional reserve for sfFinishFunction

* higher fees for EscrowFinish

* rename amendment to SmartEscrow

* make fee voting changes, add basic tests

* clean up

* clean up

* clean up

* more cleanup

* add subscribe tests

* add more tests

* undo formatting

* undo formatting

* remove bad comment

* more debugging statements

* fix clang-format

* fix rebase issues

* fix more rebase issues

* more rebase fixes

* add source code for wasm

* respond to comments

* add const

---------

Co-authored-by: Peng Wang <pwang200@gmail.com>
This commit is contained in:
Mayukha Vadari
2025-03-20 14:08:06 -04:00
committed by GitHub
parent 5bf3a308d5
commit b6a95f9970
33 changed files with 6339 additions and 40 deletions

View File

@@ -103,6 +103,7 @@ endif()
find_package(nudb REQUIRED) find_package(nudb REQUIRED)
find_package(date REQUIRED) find_package(date REQUIRED)
find_package(xxHash REQUIRED) find_package(xxHash REQUIRED)
find_package(wasmedge REQUIRED)
target_link_libraries(ripple_libs INTERFACE target_link_libraries(ripple_libs INTERFACE
ed25519::ed25519 ed25519::ed25519

View File

@@ -1248,6 +1248,30 @@
# Example: # Example:
# owner_reserve = 2000000 # 2 XRP # owner_reserve = 2000000 # 2 XRP
# #
# extension_compute_limit = <gas>
#
# The extension compute limit is the maximum amount of gas that can be
# consumed by a single transaction. The gas limit is used to prevent
# transactions from consuming too many resources.
#
# If this parameter is unspecified, rippled will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# extension_compute_limit = 2000000 # 2 million gas
#
# extension_size_limit = <bytes>
#
# The extension size limit is the maximum size of a WASM extension in
# bytes. The size limit is used to prevent extensions from consuming
# too many resources.
#
# If this parameter is unspecified, rippled will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# extension_compute_limit = 2000000 # 2 million gas
#
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# #
# 9. Misc Settings # 9. Misc Settings

View File

@@ -65,6 +65,7 @@ target_link_libraries(xrpl.imports.main
xrpl.libpb xrpl.libpb
xxHash::xxhash xxHash::xxhash
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp> $<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
wasmedge::wasmedge
) )
include(add_module) include(add_module)

View File

@@ -2,6 +2,7 @@ from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
import re import re
class Xrpl(ConanFile): class Xrpl(ConanFile):
name = 'xrpl' name = 'xrpl'
@@ -32,6 +33,7 @@ class Xrpl(ConanFile):
'soci/4.0.3', 'soci/4.0.3',
'xxhash/0.8.2', 'xxhash/0.8.2',
'zlib/1.3.1', 'zlib/1.3.1',
'wasmedge/0.14.1',
] ]
tool_requires = [ tool_requires = [
@@ -125,6 +127,7 @@ class Xrpl(ConanFile):
self.folders.generators = 'build/generators' self.folders.generators = 'build/generators'
generators = 'CMakeDeps' generators = 'CMakeDeps'
def generate(self): def generate(self):
tc = CMakeToolchain(self) tc = CMakeToolchain(self)
tc.variables['tests'] = self.options.tests tc.variables['tests'] = self.options.tests

View File

@@ -80,7 +80,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how // Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this. // the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 88; static constexpr std::size_t numFeatures = 89;
/** Amendments that this server supports and the default voting behavior. /** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated Whether they are enabled depends on the Rules defined in the validated

View File

@@ -34,6 +34,9 @@ struct Fees
XRPAmount base{0}; // Reference tx cost (drops) XRPAmount base{0}; // Reference tx cost (drops)
XRPAmount reserve{0}; // Reserve base (drops) XRPAmount reserve{0}; // Reserve base (drops)
XRPAmount increment{0}; // Reserve increment (drops) XRPAmount increment{0}; // Reserve increment (drops)
std::uint32_t extensionComputeLimit{
0}; // Extension compute limit (instructions)
std::uint32_t extensionSizeLimit{0}; // Extension size limit (bytes)
explicit Fees() = default; explicit Fees() = default;
Fees(Fees const&) = default; Fees(Fees const&) = default;

View File

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

View File

@@ -344,6 +344,7 @@ enum TECcodes : TERUnderlyingType {
tecARRAY_TOO_LARGE = 191, tecARRAY_TOO_LARGE = 191,
tecLOCKED = 192, tecLOCKED = 192,
tecBAD_CREDENTIALS = 193, tecBAD_CREDENTIALS = 193,
tecWASM_REJECTED = 194,
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@@ -29,7 +29,7 @@
// If you add an amendment here, then do not forget to increment `numFeatures` // If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h. // in include/xrpl/protocol/Feature.h.
// Check flags in Credential transactions XRPL_FEATURE(SmartEscrow, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)

View File

@@ -315,6 +315,11 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
{sfBaseFeeDrops, soeOPTIONAL}, {sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL}, {sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL}, {sfReserveIncrementDrops, soeOPTIONAL},
// New fields
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfPreviousTxnID, soeOPTIONAL}, {sfPreviousTxnID, soeOPTIONAL},
{sfPreviousTxnLgrSeq, soeOPTIONAL}, {sfPreviousTxnLgrSeq, soeOPTIONAL},
})) }))
@@ -344,6 +349,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
{sfCondition, soeOPTIONAL}, {sfCondition, soeOPTIONAL},
{sfCancelAfter, soeOPTIONAL}, {sfCancelAfter, soeOPTIONAL},
{sfFinishAfter, soeOPTIONAL}, {sfFinishAfter, soeOPTIONAL},
{sfFinishFunction, soeOPTIONAL},
{sfData, soeOPTIONAL},
{sfSourceTag, soeOPTIONAL}, {sfSourceTag, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL}, {sfDestinationTag, soeOPTIONAL},
{sfOwnerNode, soeREQUIRED}, {sfOwnerNode, soeREQUIRED},

View File

@@ -112,6 +112,8 @@ TYPED_SFIELD(sfEmitGeneration, UINT32, 46)
TYPED_SFIELD(sfVoteWeight, UINT32, 48) TYPED_SFIELD(sfVoteWeight, UINT32, 48)
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50) TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51) TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 52)
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 53)
// 64-bit integers (common) // 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1) TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -223,7 +225,7 @@ TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23) TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24) TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24)
// currency amount (AMM) // currency amount (more)
TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25) TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26) TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
TYPED_SFIELD(sfEPrice, AMOUNT, 27) TYPED_SFIELD(sfEPrice, AMOUNT, 27)
@@ -265,6 +267,7 @@ TYPED_SFIELD(sfAssetClass, VL, 28)
TYPED_SFIELD(sfProvider, VL, 29) TYPED_SFIELD(sfProvider, VL, 29)
TYPED_SFIELD(sfMPTokenMetadata, VL, 30) TYPED_SFIELD(sfMPTokenMetadata, VL, 30)
TYPED_SFIELD(sfCredentialType, VL, 31) TYPED_SFIELD(sfCredentialType, VL, 31)
TYPED_SFIELD(sfFinishFunction, VL, 32)
// account (common) // account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1) TYPED_SFIELD(sfAccount, ACCOUNT, 1)

View File

@@ -42,12 +42,14 @@ TRANSACTION(ttPAYMENT, 0, Payment, ({
/** This transaction type creates an escrow object. */ /** This transaction type creates an escrow object. */
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED}, {sfAmount, soeREQUIRED},
{sfDestination, soeREQUIRED},
{sfDestinationTag, soeOPTIONAL},
{sfCondition, soeOPTIONAL}, {sfCondition, soeOPTIONAL},
{sfCancelAfter, soeOPTIONAL}, {sfCancelAfter, soeOPTIONAL},
{sfFinishAfter, soeOPTIONAL}, {sfFinishAfter, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL}, {sfFinishFunction, soeOPTIONAL},
{sfData, soeOPTIONAL},
})) }))
/** This transaction type completes an existing escrow. */ /** This transaction type completes an existing escrow. */
@@ -488,6 +490,8 @@ TRANSACTION(ttFEE, 101, SetFee, ({
{sfBaseFeeDrops, soeOPTIONAL}, {sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL}, {sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL}, {sfReserveIncrementDrops, soeOPTIONAL},
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
})) }))
/** This system-generated transaction type is used to update the network's negative UNL /** This system-generated transaction type is used to update the network's negative UNL

View File

@@ -265,6 +265,8 @@ JSS(expected_date_UTC); // out: any (warnings)
JSS(expected_ledger_size); // out: TxQ JSS(expected_ledger_size); // out: TxQ
JSS(expiration); // out: AccountOffers, AccountChannels, JSS(expiration); // out: AccountOffers, AccountChannels,
// ValidatorList, amm_info // ValidatorList, amm_info
JSS(extension_compute); // out: NetworkOps
JSS(extension_size); // out: NetworkOps
JSS(fail_hard); // in: Sign, Submit JSS(fail_hard); // in: Sign, Submit
JSS(failed); // out: InboundLedger JSS(failed); // out: InboundLedger
JSS(feature); // in: Feature JSS(feature); // in: Feature

View File

@@ -66,6 +66,9 @@ STValidation::validationFormat()
{sfBaseFeeDrops, soeOPTIONAL}, {sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL}, {sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL}, {sfReserveIncrementDrops, soeOPTIONAL},
// featureSmartEscrow
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
}; };
// clang-format on // clang-format on

View File

@@ -117,6 +117,7 @@ transResults()
MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."), MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."),
MAKE_ERROR(tecLOCKED, "Fund is locked."), MAKE_ERROR(tecLOCKED, "Fund is locked."),
MAKE_ERROR(tecBAD_CREDENTIALS, "Bad credentials."), MAKE_ERROR(tecBAD_CREDENTIALS, "Bad credentials."),
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(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),

View File

@@ -1519,7 +1519,7 @@ struct Escrow_test : public beast::unit_test::suite
Account const alice{"alice"}; Account const alice{"alice"};
Account const bob{"bob"}; Account const bob{"bob"};
Account const carol{"carol"}; Account const carol{"carol"};
Account const dillon{"dillon "}; Account const dillon{"dillon"};
Account const zelda{"zelda"}; Account const zelda{"zelda"};
const char credType[] = "abcde"; const char credType[] = "abcde";
@@ -1656,6 +1656,370 @@ struct Escrow_test : public beast::unit_test::suite
} }
} }
void
testFinishFunctionPreflight()
{
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
// #[no_mangle]
// pub fn ready() -> bool {
// unsafe { host_lib::get_ledger_sqn() >= 5}
// }
static auto wasmHex =
"0061736d010000000105016000017f021b0108686f73745f6c69620e6765745f6c"
"65646765725f73716e0000030201000405017001010105030100100619037f0141"
"8080c0000b7f00418080c0000b7f00418080c0000b072d04066d656d6f72790200"
"05726561647900010a5f5f646174615f656e6403010b5f5f686561705f62617365"
"03020a0d010b0010808080800041044a0b006e046e616d65000e0d7761736d5f6c"
"69622e7761736d01430200395f5a4e387761736d5f6c696238686f73745f6c6962"
"31346765745f6c65646765725f73716e3137686663383539386237646539633036"
"64624501057265616479071201000f5f5f737461636b5f706f696e746572005509"
"70726f64756365727302086c616e6775616765010452757374000c70726f636573"
"7365642d62790105727573746325312e38332e302d6e696768746c792028633266"
"37346333663920323032342d30392d30392900490f7461726765745f6665617475"
"726573042b0a6d756c746976616c75652b0f6d757461626c652d676c6f62616c73"
"2b0f7265666572656e63652d74797065732b087369676e2d657874";
{
// featureSmartEscrow disabled
Env env(*this, supported_amendments() - featureSmartEscrow);
env.fund(XRP(5000), alice, carol);
XRPAmount const txnFees = env.current()->fees().base + 1000;
auto escrowCreate = escrow(alice, carol, XRP(1000));
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
fee(txnFees),
ter(temDISABLED));
env.close();
}
Env env(*this);
XRPAmount const txnFees = env.current()->fees().base + 1000;
// create escrow
env.fund(XRP(5000), alice, carol);
auto escrowCreate = escrow(alice, carol, XRP(1000));
// Success situations
{
// FinishFunction + CancelAfter
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
fee(txnFees));
env.close();
}
{
// FinishFunction + Condition + CancelAfter
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
condition(cb1),
fee(txnFees));
env.close();
}
{
// FinishFunction + FinishAfter + CancelAfter
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
finish_time(env.now() + 2s),
fee(txnFees));
env.close();
}
{
// FinishFunction + FinishAfter + Condition + CancelAfter
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
condition(cb1),
finish_time(env.now() + 2s),
fee(txnFees));
env.close();
}
// Failure situations (i.e. all other combinations)
{
// only FinishFunction
env(escrowCreate,
finish_function(wasmHex),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + FinishAfter
env(escrowCreate,
finish_function(wasmHex),
finish_time(env.now() + 2s),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + Condition
env(escrowCreate,
finish_function(wasmHex),
condition(cb1),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + FinishAfter + Condition
env(escrowCreate,
finish_function(wasmHex),
condition(cb1),
finish_time(env.now() + 2s),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction 0 length
env(escrowCreate,
finish_function(""),
cancel_time(env.now() + 100s),
fee(txnFees),
ter(temMALFORMED));
env.close();
}
// {
// // FinishFunction > max length
// std::string longWasmHex = "00";
// // TODO: fix to use the config setting
// // TODO: make this test more efficient
// // uncomment when that's done
// for (int i = 0; i < 4294967295; i++)
// {
// longWasmHex += "11";
// }
// env(escrowCreate,
// finish_function(longWasmHex),
// cancel_time(env.now() + 100s),
// fee(txnFees),
// ter(temMALFORMED));
// env.close();
// }
}
void
testFinishFunction()
{
testcase("PoC escrow function");
using namespace jtx;
using namespace std::chrono;
Account const alice{"alice"};
Account const carol{"carol"};
// Tests whether the ledger index is >= 5
// #[no_mangle]
// pub fn ready() -> bool {
// unsafe { host_lib::get_ledger_sqn() >= 5}
// }
static auto wasmHex =
"0061736d010000000105016000017f021b0108686f73745f6c69620e6765745f6c"
"65646765725f73716e0000030201000405017001010105030100100619037f0141"
"8080c0000b7f00418080c0000b7f00418080c0000b072d04066d656d6f72790200"
"05726561647900010a5f5f646174615f656e6403010b5f5f686561705f62617365"
"03020a0d010b0010808080800041044a0b006e046e616d65000e0d7761736d5f6c"
"69622e7761736d01430200395f5a4e387761736d5f6c696238686f73745f6c6962"
"31346765745f6c65646765725f73716e3137686663383539386237646539633036"
"64624501057265616479071201000f5f5f737461636b5f706f696e746572005509"
"70726f64756365727302086c616e6775616765010452757374000c70726f636573"
"7365642d62790105727573746325312e38332e302d6e696768746c792028633266"
"37346333663920323032342d30392d30392900490f7461726765745f6665617475"
"726573042b0a6d756c746976616c75652b0f6d757461626c652d676c6f62616c73"
"2b0f7265666572656e63652d74797065732b087369676e2d657874";
{
// basic FinishFunction situation
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
fee(txnFees));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
env(finish(carol, alice, seq),
fee(txnFees),
ter(tecWASM_REJECTED));
env(finish(alice, alice, seq),
fee(txnFees),
ter(tecWASM_REJECTED));
env(finish(alice, alice, seq),
fee(txnFees),
ter(tecWASM_REJECTED));
env(finish(carol, alice, seq),
fee(txnFees),
ter(tecWASM_REJECTED));
env(finish(carol, alice, seq),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
env(finish(alice, alice, seq), fee(txnFees), ter(tesSUCCESS));
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
{
// FinishFunction + Condition
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
env(escrowCreate,
finish_function(wasmHex),
condition(cb1),
cancel_time(env.now() + 100s),
fee(txnFees));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
// no fulfillment provided, function fails
env(finish(carol, alice, seq),
fee(txnFees),
ter(tecCRYPTOCONDITION_ERROR));
// fulfillment provided, function fails
env(finish(carol, alice, seq),
condition(cb1),
fulfillment(fb1),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
// no fulfillment provided, function succeeds
env(finish(alice, alice, seq),
fee(txnFees),
ter(tecCRYPTOCONDITION_ERROR));
// wrong fulfillment provided, function succeeds
env(finish(alice, alice, seq),
condition(cb1),
fulfillment(fb2),
fee(txnFees),
ter(tecCRYPTOCONDITION_ERROR));
// fulfillment provided, function succeeds, tx succeeds
env(finish(alice, alice, seq),
condition(cb1),
fulfillment(fb1),
fee(txnFees),
ter(tesSUCCESS));
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
{
// FinishFunction + FinishAfter
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
auto const ts = env.now() + 97s;
env(escrowCreate,
finish_function(wasmHex),
finish_time(ts),
cancel_time(env.now() + 1000s),
fee(txnFees));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
// finish time hasn't passed, function fails
env(finish(carol, alice, seq),
fee(txnFees + 1),
ter(tecNO_PERMISSION));
env.close();
// finish time hasn't passed, function succeeds
for (; env.now() < ts; env.close())
env(finish(carol, alice, seq),
fee(txnFees + 2),
ter(tecNO_PERMISSION));
env(finish(carol, alice, seq),
fee(txnFees + 1),
ter(tesSUCCESS));
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
{
// FinishFunction + FinishAfter #2
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
env(escrowCreate,
finish_function(wasmHex),
finish_time(env.now() + 2s),
cancel_time(env.now() + 100s),
fee(txnFees));
// Don't close the ledger here
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
// finish time hasn't passed, function fails
env(finish(carol, alice, seq),
fee(txnFees),
ter(tecNO_PERMISSION));
env.close();
// finish time has passed, function fails
env(finish(carol, alice, seq),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
// finish time has passed, function succeeds, tx succeeds
env(finish(carol, alice, seq), fee(txnFees), ter(tesSUCCESS));
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
}
void void
run() override run() override
{ {
@@ -1671,6 +2035,8 @@ struct Escrow_test : public beast::unit_test::suite
testConsequences(); testConsequences();
testEscrowWithTickets(); testEscrowWithTickets();
testCredentials(); testCredentials();
testFinishFunctionPreflight();
testFinishFunction();
} }
}; };

View File

@@ -37,36 +37,56 @@ class FeeVote_test : public beast::unit_test::suite
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee); BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve); BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve); BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
BEAST_EXPECT(
setup.extension_compute_limit ==
defaultSetup.extension_compute_limit);
BEAST_EXPECT(
setup.extension_size_limit ==
defaultSetup.extension_size_limit);
} }
{ {
Section config; Section config;
config.append( config.append(
{"reference_fee = 50", {"reference_fee = 50",
"account_reserve = 1234567", "account_reserve = 1234567",
"owner_reserve = 1234"}); "owner_reserve = 1234",
"extension_compute_limit = 100",
"extension_size_limit = 200"});
auto setup = setup_FeeVote(config); auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == 50); BEAST_EXPECT(setup.reference_fee == 50);
BEAST_EXPECT(setup.account_reserve == 1234567); BEAST_EXPECT(setup.account_reserve == 1234567);
BEAST_EXPECT(setup.owner_reserve == 1234); BEAST_EXPECT(setup.owner_reserve == 1234);
BEAST_EXPECT(setup.extension_compute_limit == 100);
BEAST_EXPECT(setup.extension_size_limit == 200);
} }
{ {
Section config; Section config;
config.append( config.append(
{"reference_fee = blah", {"reference_fee = blah",
"account_reserve = yada", "account_reserve = yada",
"owner_reserve = foo"}); "owner_reserve = foo",
"extension_compute_limit = bar",
"extension_size_limit = baz"});
// Illegal values are ignored, and the defaults left unchanged // Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config); auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee); BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve); BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve); BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
BEAST_EXPECT(
setup.extension_compute_limit ==
defaultSetup.extension_compute_limit);
BEAST_EXPECT(
setup.extension_size_limit ==
defaultSetup.extension_size_limit);
} }
{ {
Section config; Section config;
config.append( config.append(
{"reference_fee = -50", {"reference_fee = -50",
"account_reserve = -1234567", "account_reserve = -1234567",
"owner_reserve = -1234"}); "owner_reserve = -1234",
"extension_compute_limit = -100",
"extension_size_limit = -200"});
// Illegal values are ignored, and the defaults left unchanged // Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config); auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee); BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
@@ -74,6 +94,11 @@ class FeeVote_test : public beast::unit_test::suite
setup.account_reserve == static_cast<std::uint32_t>(-1234567)); setup.account_reserve == static_cast<std::uint32_t>(-1234567));
BEAST_EXPECT( BEAST_EXPECT(
setup.owner_reserve == static_cast<std::uint32_t>(-1234)); setup.owner_reserve == static_cast<std::uint32_t>(-1234));
BEAST_EXPECT(
setup.extension_compute_limit ==
static_cast<std::uint32_t>(-100));
BEAST_EXPECT(
setup.extension_size_limit == static_cast<std::uint32_t>(-200));
} }
{ {
const auto big64 = std::to_string( const auto big64 = std::to_string(
@@ -84,12 +109,20 @@ class FeeVote_test : public beast::unit_test::suite
config.append( config.append(
{"reference_fee = " + big64, {"reference_fee = " + big64,
"account_reserve = " + big64, "account_reserve = " + big64,
"owner_reserve = " + big64}); "owner_reserve = " + big64,
"extension_compute_limit = " + big64,
"extension_size_limit = " + big64});
// Illegal values are ignored, and the defaults left unchanged // Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config); auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee); BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve); BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve); BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
BEAST_EXPECT(
setup.extension_compute_limit ==
defaultSetup.extension_compute_limit);
BEAST_EXPECT(
setup.extension_size_limit ==
defaultSetup.extension_size_limit);
} }
} }

View File

@@ -48,6 +48,11 @@ struct PseudoTx_test : public beast::unit_test::suite
obj[sfReserveIncrement] = 0; obj[sfReserveIncrement] = 0;
obj[sfReferenceFeeUnits] = 0; obj[sfReferenceFeeUnits] = 0;
} }
if (rules.enabled(featureSmartEscrow))
{
obj[sfExtensionComputeLimit] = 0;
obj[sfExtensionSizeLimit] = 0;
}
})); }));
res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) { res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) {
@@ -114,9 +119,10 @@ struct PseudoTx_test : public beast::unit_test::suite
{ {
using namespace test::jtx; using namespace test::jtx;
FeatureBitset const all{supported_amendments()}; FeatureBitset const all{supported_amendments()};
FeatureBitset const xrpFees{featureXRPFees};
testPrevented(all - featureXRPFees - featureSmartEscrow);
testPrevented(all - featureXRPFees); testPrevented(all - featureXRPFees);
testPrevented(all - featureSmartEscrow);
testPrevented(all); testPrevented(all);
testAllowed(); testAllowed();
} }

4752
src/test/app/Wasm_test.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -343,6 +343,33 @@ public:
} }
}; };
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_;
}
};
/* Payment Channel */ /* Payment Channel */
/******************************************************************************/ /******************************************************************************/

View File

@@ -36,6 +36,8 @@ setupConfigForUnitTests(Config& cfg)
cfg.FEES.reference_fee = 10; cfg.FEES.reference_fee = 10;
cfg.FEES.account_reserve = XRP(200).value().xrp().drops(); cfg.FEES.account_reserve = XRP(200).value().xrp().drops();
cfg.FEES.owner_reserve = XRP(50).value().xrp().drops(); cfg.FEES.owner_reserve = XRP(50).value().xrp().drops();
cfg.FEES.extension_compute_limit = 4294967295;
cfg.FEES.extension_size_limit = 4294967295;
// The Beta API (currently v2) is always available to tests // The Beta API (currently v2) is always available to tests
cfg.BETA_RPC_API = true; cfg.BETA_RPC_API = true;

View File

@@ -515,6 +515,22 @@ public:
if (jv.isMember(jss::reserve_inc) != isFlagLedger) if (jv.isMember(jss::reserve_inc) != isFlagLedger)
return false; return false;
if (env.closed()->rules().enabled(featureSmartEscrow))
{
if (jv.isMember(jss::extension_compute) != isFlagLedger)
return false;
if (jv.isMember(jss::extension_size) != isFlagLedger)
return false;
}
else
{
if (jv.isMember(jss::extension_compute))
return false;
if (jv.isMember(jss::extension_size))
return false;
}
return true; return true;
}; };
@@ -1298,14 +1314,14 @@ public:
{ {
using namespace test::jtx; using namespace test::jtx;
FeatureBitset const all{supported_amendments()}; FeatureBitset const all{supported_amendments()};
FeatureBitset const xrpFees{featureXRPFees};
testServer(); testServer();
testLedger(); testLedger();
testTransactions_APIv1(); testTransactions_APIv1();
testTransactions_APIv2(); testTransactions_APIv2();
testManifests(); testManifests();
testValidations(all - xrpFees); testValidations(all - featureXRPFees - featureSmartEscrow);
testValidations(all - featureSmartEscrow);
testValidations(all); testValidations(all);
testSubErrors(true); testSubErrors(true);
testSubErrors(false); testSubErrors(false);

View File

@@ -230,6 +230,14 @@ Ledger::Ledger(
sle->at(sfReserveIncrement) = *f; sle->at(sfReserveIncrement) = *f;
sle->at(sfReferenceFeeUnits) = Config::FEE_UNITS_DEPRECATED; sle->at(sfReferenceFeeUnits) = Config::FEE_UNITS_DEPRECATED;
} }
if (std::find(
amendments.begin(), amendments.end(), featureSmartEscrow) !=
amendments.end())
{
sle->at(sfExtensionComputeLimit) =
config.FEES.extension_compute_limit;
sle->at(sfExtensionSizeLimit) = config.FEES.extension_size_limit;
}
rawInsert(sle); rawInsert(sle);
} }
@@ -621,6 +629,7 @@ Ledger::setup()
{ {
bool oldFees = false; bool oldFees = false;
bool newFees = false; bool newFees = false;
bool extensionFees = false;
{ {
auto const baseFee = sle->at(~sfBaseFee); auto const baseFee = sle->at(~sfBaseFee);
auto const reserveBase = sle->at(~sfReserveBase); auto const reserveBase = sle->at(~sfReserveBase);
@@ -638,6 +647,7 @@ Ledger::setup()
auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops); auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops);
auto const reserveIncrementXRP = auto const reserveIncrementXRP =
sle->at(~sfReserveIncrementDrops); sle->at(~sfReserveIncrementDrops);
auto assign = [&ret]( auto assign = [&ret](
XRPAmount& dest, XRPAmount& dest,
std::optional<STAmount> const& src) { std::optional<STAmount> const& src) {
@@ -654,12 +664,32 @@ Ledger::setup()
assign(fees_.increment, reserveIncrementXRP); assign(fees_.increment, reserveIncrementXRP);
newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP; newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP;
} }
{
auto const extensionComputeLimit =
sle->at(~sfExtensionComputeLimit);
auto const extensionSizeLimit = sle->at(~sfExtensionSizeLimit);
auto assign = [](std::uint32_t& dest,
std::optional<std::uint32_t> const& src) {
if (src)
{
dest = src.value();
}
};
assign(fees_.extensionComputeLimit, extensionComputeLimit);
assign(fees_.extensionSizeLimit, extensionSizeLimit);
extensionFees = extensionComputeLimit || extensionSizeLimit;
}
if (oldFees && newFees) if (oldFees && newFees)
// Should be all of one or the other, but not both // Should be all of one or the other, but not both
ret = false; ret = false;
if (!rules_.enabled(featureXRPFees) && newFees) if (!rules_.enabled(featureXRPFees) && newFees)
// Can't populate the new fees before the amendment is enabled // Can't populate the new fees before the amendment is enabled
ret = false; ret = false;
if (!rules_.enabled(featureSmartEscrow) && extensionFees)
// Can't populate the extension fees before the amendment is
// enabled
ret = false;
} }
} }
catch (SHAMapMissingNode const&) catch (SHAMapMissingNode const&)
@@ -678,15 +708,17 @@ Ledger::setup()
void void
Ledger::defaultFees(Config const& config) Ledger::defaultFees(Config const& config)
{ {
XRPL_ASSERT( assert(
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0, fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0 &&
"ripple::Ledger::defaultFees : zero fees"); fees_.extensionComputeLimit == 0 && fees_.extensionSizeLimit == 0);
if (fees_.base == 0) if (fees_.base == 0)
fees_.base = config.FEES.reference_fee; fees_.base = config.FEES.reference_fee;
if (fees_.reserve == 0) if (fees_.reserve == 0)
fees_.reserve = config.FEES.account_reserve; fees_.reserve = config.FEES.account_reserve;
if (fees_.increment == 0) if (fees_.extensionComputeLimit == 0)
fees_.increment = config.FEES.owner_reserve; fees_.extensionComputeLimit = config.FEES.extension_compute_limit;
if (fees_.extensionSizeLimit == 0)
fees_.extensionSizeLimit = config.FEES.extension_size_limit;
} }
std::shared_ptr<SLE> std::shared_ptr<SLE>

View File

@@ -29,10 +29,10 @@ namespace ripple {
namespace detail { namespace detail {
template <typename value_type>
class VotableValue class VotableValue
{ {
private: private:
using value_type = XRPAmount;
value_type const current_; // The current setting value_type const current_; // The current setting
value_type const target_; // The setting we want value_type const target_; // The setting we want
std::map<value_type, int> voteMap_; std::map<value_type, int> voteMap_;
@@ -67,8 +67,9 @@ public:
getVotes() const; getVotes() const;
}; };
auto template <typename value_type>
VotableValue::getVotes() const -> std::pair<value_type, bool> std::pair<value_type, bool>
VotableValue<value_type>::getVotes() const
{ {
value_type ourVote = current_; value_type ourVote = current_;
int weight = 0; int weight = 0;
@@ -191,6 +192,32 @@ FeeVoteImpl::doValidation(
"reserve increment", "reserve increment",
sfReserveIncrement); sfReserveIncrement);
} }
if (rules.enabled(featureSmartEscrow))
{
auto vote = [&v, this](
auto const current,
std::uint32_t target,
const char* name,
auto const& sfield) {
if (current != target)
{
JLOG(journal_.info())
<< "Voting for " << name << " of " << target;
v[sfield] = target;
}
};
vote(
lastFees.extensionComputeLimit,
target_.extension_compute_limit,
"extension compute limit",
sfExtensionComputeLimit);
vote(
lastFees.extensionSizeLimit,
target_.extension_size_limit,
"extension size limit",
sfExtensionSizeLimit);
}
} }
void void
@@ -213,11 +240,19 @@ FeeVoteImpl::doVoting(
detail::VotableValue incReserveVote( detail::VotableValue incReserveVote(
lastClosedLedger->fees().increment, target_.owner_reserve); lastClosedLedger->fees().increment, target_.owner_reserve);
detail::VotableValue extensionComputeVote(
lastClosedLedger->fees().extensionComputeLimit,
target_.extension_compute_limit);
detail::VotableValue extensionSizeVote(
lastClosedLedger->fees().extensionSizeLimit,
target_.extension_size_limit);
auto const& rules = lastClosedLedger->rules(); auto const& rules = lastClosedLedger->rules();
if (rules.enabled(featureXRPFees)) if (rules.enabled(featureXRPFees))
{ {
auto doVote = [](std::shared_ptr<STValidation> const& val, auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue& value, detail::VotableValue<XRPAmount>& value,
SF_AMOUNT const& xrpField) { SF_AMOUNT const& xrpField) {
if (auto const field = ~val->at(~xrpField); if (auto const field = ~val->at(~xrpField);
field && field->native()) field && field->native())
@@ -246,7 +281,7 @@ FeeVoteImpl::doVoting(
else else
{ {
auto doVote = [](std::shared_ptr<STValidation> const& val, auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue& value, detail::VotableValue<XRPAmount>& value,
auto const& valueField) { auto const& valueField) {
if (auto const field = val->at(~valueField)) if (auto const field = val->at(~valueField))
{ {
@@ -277,6 +312,29 @@ FeeVoteImpl::doVoting(
doVote(val, incReserveVote, sfReserveIncrement); doVote(val, incReserveVote, sfReserveIncrement);
} }
} }
if (rules.enabled(featureSmartEscrow))
{
auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue<std::uint32_t>& value,
SF_UINT32 const& extensionField) {
if (auto const field = ~val->at(~extensionField); field)
{
value.addVote(field.value());
}
else
{
value.noVote();
}
};
for (auto const& val : set)
{
if (!val->isTrusted())
continue;
doVote(val, extensionComputeVote, sfExtensionComputeLimit);
doVote(val, extensionSizeVote, sfExtensionSizeLimit);
}
}
// choose our positions // choose our positions
// TODO: Use structured binding once LLVM 16 is the minimum supported // TODO: Use structured binding once LLVM 16 is the minimum supported
@@ -285,11 +343,14 @@ FeeVoteImpl::doVoting(
auto const baseFee = baseFeeVote.getVotes(); auto const baseFee = baseFeeVote.getVotes();
auto const baseReserve = baseReserveVote.getVotes(); auto const baseReserve = baseReserveVote.getVotes();
auto const incReserve = incReserveVote.getVotes(); auto const incReserve = incReserveVote.getVotes();
auto const extensionCompute = extensionComputeVote.getVotes();
auto const extensionSize = extensionSizeVote.getVotes();
auto const seq = lastClosedLedger->info().seq + 1; auto const seq = lastClosedLedger->info().seq + 1;
// add transactions to our position // add transactions to our position
if (baseFee.second || baseReserve.second || incReserve.second) if (baseFee.second || baseReserve.second || incReserve.second ||
extensionCompute.second || extensionSize.second)
{ {
JLOG(journal_.warn()) JLOG(journal_.warn())
<< "We are voting for a fee change: " << baseFee.first << "/" << "We are voting for a fee change: " << baseFee.first << "/"
@@ -317,6 +378,11 @@ FeeVoteImpl::doVoting(
incReserveVote.current()); incReserveVote.current());
obj[sfReferenceFeeUnits] = Config::FEE_UNITS_DEPRECATED; obj[sfReferenceFeeUnits] = Config::FEE_UNITS_DEPRECATED;
} }
if (rules.enabled(featureSmartEscrow))
{
obj[sfExtensionComputeLimit] = extensionCompute.first;
obj[sfExtensionSizeLimit] = extensionSize.first;
}
}); });
uint256 txID = feeTx.getTransactionID(); uint256 txID = feeTx.getTransactionID();

View File

@@ -2262,6 +2262,15 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> const& val)
reserveIncXRP && reserveIncXRP->native()) reserveIncXRP && reserveIncXRP->native())
jvObj[jss::reserve_inc] = reserveIncXRP->xrp().jsonClipped(); jvObj[jss::reserve_inc] = reserveIncXRP->xrp().jsonClipped();
if (auto const extensionComputeLimit =
~val->at(~sfExtensionComputeLimit);
extensionComputeLimit)
jvObj[jss::extension_compute] = *extensionComputeLimit;
if (auto const extensionSizeLimit = ~val->at(~sfExtensionSizeLimit);
extensionSizeLimit)
jvObj[jss::extension_size] = *extensionSizeLimit;
// NOTE Use MultiApiJson to publish two slightly different JSON objects // NOTE Use MultiApiJson to publish two slightly different JSON objects
// for consumers supporting different API versions // for consumers supporting different API versions
MultiApiJson multiObj{jvObj}; MultiApiJson multiObj{jvObj};
@@ -2711,12 +2720,21 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
l[jss::seq] = Json::UInt(lpClosed->info().seq); l[jss::seq] = Json::UInt(lpClosed->info().seq);
l[jss::hash] = to_string(lpClosed->info().hash); l[jss::hash] = to_string(lpClosed->info().hash);
bool const smartEscrowEnabled =
m_ledgerMaster.getValidatedLedger()->rules().enabled(
featureSmartEscrow);
if (!human) if (!human)
{ {
l[jss::base_fee] = baseFee.jsonClipped(); l[jss::base_fee] = baseFee.jsonClipped();
l[jss::reserve_base] = l[jss::reserve_base] =
lpClosed->fees().accountReserve(0).jsonClipped(); lpClosed->fees().accountReserve(0).jsonClipped();
l[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped(); l[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
if (smartEscrowEnabled)
{
l[jss::extension_compute] =
lpClosed->fees().extensionComputeLimit;
l[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
}
l[jss::close_time] = Json::Value::UInt( l[jss::close_time] = Json::Value::UInt(
lpClosed->info().closeTime.time_since_epoch().count()); lpClosed->info().closeTime.time_since_epoch().count());
} }
@@ -2726,6 +2744,12 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
l[jss::reserve_base_xrp] = l[jss::reserve_base_xrp] =
lpClosed->fees().accountReserve(0).decimalXRP(); lpClosed->fees().accountReserve(0).decimalXRP();
l[jss::reserve_inc_xrp] = lpClosed->fees().increment.decimalXRP(); l[jss::reserve_inc_xrp] = lpClosed->fees().increment.decimalXRP();
if (smartEscrowEnabled)
{
l[jss::extension_compute] =
lpClosed->fees().extensionComputeLimit;
l[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
}
if (auto const closeOffset = app_.timeKeeper().closeOffset(); if (auto const closeOffset = app_.timeKeeper().closeOffset();
std::abs(closeOffset.count()) >= 60) std::abs(closeOffset.count()) >= 60)
@@ -2911,6 +2935,13 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
lpAccepted->fees().accountReserve(0).jsonClipped(); lpAccepted->fees().accountReserve(0).jsonClipped();
jvObj[jss::reserve_inc] = jvObj[jss::reserve_inc] =
lpAccepted->fees().increment.jsonClipped(); lpAccepted->fees().increment.jsonClipped();
if (lpAccepted->rules().enabled(featureSmartEscrow))
{
jvObj[jss::extension_compute] =
lpAccepted->fees().extensionComputeLimit;
jvObj[jss::extension_size] =
lpAccepted->fees().extensionSizeLimit;
}
jvObj[jss::txn_count] = Json::UInt(alpAccepted->size()); jvObj[jss::txn_count] = Json::UInt(alpAccepted->size());
@@ -3947,6 +3978,12 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult)
jvResult[jss::reserve_base] = jvResult[jss::reserve_base] =
lpClosed->fees().accountReserve(0).jsonClipped(); lpClosed->fees().accountReserve(0).jsonClipped();
jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped(); jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
if (lpClosed->rules().enabled(featureSmartEscrow))
{
jvResult[jss::extension_compute] =
lpClosed->fees().extensionComputeLimit;
jvResult[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
}
} }
if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger()) if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger())

View File

@@ -0,0 +1,592 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2020 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/app/misc/WasmVM.h>
// WasmVM::WasmVM(beast::Journal j)
// : j_(j)
//{
// }
namespace ripple {
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
int32_t input)
{
WasmEdge_VMContext* VMCxt = WasmEdge_VMCreate(NULL, NULL);
WasmEdge_Value Params[1] = {WasmEdge_ValueGenI32(input)};
WasmEdge_Value Returns[1];
WasmEdge_String FuncName = WasmEdge_StringCreateByCString(funcName.c_str());
WasmEdge_Result Res = WasmEdge_VMRunWasmFromBuffer(
VMCxt,
wasmCode.data(),
wasmCode.size(),
FuncName,
Params,
1,
Returns,
1);
bool ok = WasmEdge_ResultOK(Res);
bool re = false;
if (ok)
{
auto result = WasmEdge_ValueGetI32(Returns[0]);
// printf("Get the result: %d\n", result);
if (result != 0)
re = true;
}
else
{
printf("Error message: %s\n", WasmEdge_ResultGetMessage(Res));
}
WasmEdge_VMDelete(VMCxt);
WasmEdge_StringDelete(FuncName);
if (ok)
return re;
else
return Unexpected<TER>(tecFAILED_PROCESSING);
}
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
std::vector<uint8_t> const& accountID)
{
auto dataLen = (int32_t)accountID.size();
// printf("accountID size: %d\n", dataLen);
WasmEdge_VMContext* VMCxt = WasmEdge_VMCreate(NULL, NULL);
WasmEdge_Value allocParams[1] = {WasmEdge_ValueGenI32(dataLen)};
WasmEdge_Value allocReturns[1];
WasmEdge_String allocFunc = WasmEdge_StringCreateByCString("allocate");
WasmEdge_Result allocRes = WasmEdge_VMRunWasmFromBuffer(
VMCxt,
wasmCode.data(),
wasmCode.size(),
allocFunc,
allocParams,
1,
allocReturns,
1);
bool ok = WasmEdge_ResultOK(allocRes);
bool re = false;
if (ok)
{
auto pointer = WasmEdge_ValueGetI32(allocReturns[0]);
// printf("Alloc pointer: %d\n", pointer);
const WasmEdge_ModuleInstanceContext* m =
WasmEdge_VMGetActiveModule(VMCxt);
WasmEdge_String mName = WasmEdge_StringCreateByCString("memory");
WasmEdge_MemoryInstanceContext* mi =
WasmEdge_ModuleInstanceFindMemory(m, mName);
WasmEdge_Result setRes = WasmEdge_MemoryInstanceSetData(
mi, accountID.data(), pointer, dataLen);
ok = WasmEdge_ResultOK(setRes);
if (ok)
{
// printf("Set data ok\n");
WasmEdge_Value params[2] = {
WasmEdge_ValueGenI32(pointer), WasmEdge_ValueGenI32(dataLen)};
WasmEdge_Value returns[1];
WasmEdge_String func =
WasmEdge_StringCreateByCString(funcName.c_str());
WasmEdge_Result funcRes =
WasmEdge_VMExecute(VMCxt, func, params, 2, returns, 1);
ok = WasmEdge_ResultOK(funcRes);
if (ok)
{
// printf("func ok\n");
re = (WasmEdge_ValueGetI32(returns[0]) == 1);
}
else
{
printf(
"Func message: %s\n", WasmEdge_ResultGetMessage(funcRes));
}
}
else
{
printf(
"Set error message: %s\n", WasmEdge_ResultGetMessage(setRes));
}
}
else
{
printf(
"Alloc error message: %s\n", WasmEdge_ResultGetMessage(allocRes));
}
WasmEdge_VMDelete(VMCxt);
// TODO free everything
// WasmEdge_StringDelete(FuncName);
if (ok)
{
// printf("runEscrowWasm ok, result %d\n", re);
return re;
}
else
return Unexpected<TER>(tecFAILED_PROCESSING);
}
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
std::vector<uint8_t> const& escrow_tx_json_data,
std::vector<uint8_t> const& escrow_lo_json_data)
{
WasmEdge_VMContext* VMCxt = WasmEdge_VMCreate(NULL, NULL);
WasmEdge_Result loadRes =
WasmEdge_VMLoadWasmFromBuffer(VMCxt, wasmCode.data(), wasmCode.size());
if (!WasmEdge_ResultOK(loadRes))
{
printf("load error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Result validateRes = WasmEdge_VMValidate(VMCxt);
if (!WasmEdge_ResultOK(validateRes))
{
printf("validate error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Result instantiateRes = WasmEdge_VMInstantiate(VMCxt);
if (!WasmEdge_ResultOK(instantiateRes))
{
printf("instantiate error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
auto wasmAlloc = [VMCxt](std::vector<uint8_t> const& data) -> int32_t {
auto dataLen = (int32_t)data.size();
WasmEdge_Value allocParams[1] = {WasmEdge_ValueGenI32(dataLen)};
WasmEdge_Value allocReturns[1];
WasmEdge_String allocFunc = WasmEdge_StringCreateByCString("allocate");
WasmEdge_Result allocRes = WasmEdge_VMExecute(
VMCxt, allocFunc, allocParams, 1, allocReturns, 1);
if (WasmEdge_ResultOK(allocRes))
{
auto pointer = WasmEdge_ValueGetI32(allocReturns[0]);
// printf("alloc ptr %d, len %d\n", pointer, dataLen);
const WasmEdge_ModuleInstanceContext* m =
WasmEdge_VMGetActiveModule(VMCxt);
WasmEdge_String mName = WasmEdge_StringCreateByCString("memory");
WasmEdge_MemoryInstanceContext* mi =
WasmEdge_ModuleInstanceFindMemory(m, mName);
WasmEdge_Result setRes = WasmEdge_MemoryInstanceSetData(
mi, data.data(), pointer, dataLen);
if (WasmEdge_ResultOK(setRes))
{
return pointer;
}
}
return 0;
};
auto tx_ptr = wasmAlloc(escrow_tx_json_data);
auto lo_ptr = wasmAlloc(escrow_lo_json_data);
if (tx_ptr == 0 || lo_ptr == 0)
{
printf("data error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
auto txLen = (int32_t)escrow_tx_json_data.size();
auto loLen = (int32_t)escrow_lo_json_data.size();
WasmEdge_Value params[4] = {
WasmEdge_ValueGenI32(tx_ptr),
WasmEdge_ValueGenI32(txLen),
WasmEdge_ValueGenI32(lo_ptr),
WasmEdge_ValueGenI32(loLen)};
WasmEdge_Value returns[1];
WasmEdge_String func = WasmEdge_StringCreateByCString(funcName.c_str());
WasmEdge_Result funcRes =
WasmEdge_VMExecute(VMCxt, func, params, 4, returns, 1);
if (WasmEdge_ResultOK(funcRes))
{
// printf("func ok\n");
return WasmEdge_ValueGetI32(returns[0]) == 1;
}
else
{
printf("Func message: %s\n", WasmEdge_ResultGetMessage(funcRes));
return Unexpected<TER>(tecFAILED_PROCESSING);
}
}
Expected<std::pair<bool, std::string>, TER>
runEscrowWasmP4(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
std::vector<uint8_t> const& escrow_tx_json_data,
std::vector<uint8_t> const& escrow_lo_json_data)
{
WasmEdge_VMContext* VMCxt = WasmEdge_VMCreate(NULL, NULL);
WasmEdge_Result loadRes =
WasmEdge_VMLoadWasmFromBuffer(VMCxt, wasmCode.data(), wasmCode.size());
if (!WasmEdge_ResultOK(loadRes))
{
printf("load error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Result validateRes = WasmEdge_VMValidate(VMCxt);
if (!WasmEdge_ResultOK(validateRes))
{
printf("validate error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Result instantiateRes = WasmEdge_VMInstantiate(VMCxt);
if (!WasmEdge_ResultOK(instantiateRes))
{
printf("instantiate error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
auto wasmAlloc = [VMCxt](std::vector<uint8_t> const& data) -> int32_t {
auto dataLen = (int32_t)data.size();
WasmEdge_Value allocParams[1] = {WasmEdge_ValueGenI32(dataLen)};
WasmEdge_Value allocReturns[1];
WasmEdge_String allocFunc = WasmEdge_StringCreateByCString("allocate");
WasmEdge_Result allocRes = WasmEdge_VMExecute(
VMCxt, allocFunc, allocParams, 1, allocReturns, 1);
if (WasmEdge_ResultOK(allocRes))
{
auto pointer = WasmEdge_ValueGetI32(allocReturns[0]);
// printf("alloc ptr %d, len %d\n", pointer, dataLen);
const WasmEdge_ModuleInstanceContext* m =
WasmEdge_VMGetActiveModule(VMCxt);
WasmEdge_String mName = WasmEdge_StringCreateByCString("memory");
WasmEdge_MemoryInstanceContext* mi =
WasmEdge_ModuleInstanceFindMemory(m, mName);
WasmEdge_Result setRes = WasmEdge_MemoryInstanceSetData(
mi, data.data(), pointer, dataLen);
if (WasmEdge_ResultOK(setRes))
{
return pointer;
}
}
return 0;
};
auto tx_ptr = wasmAlloc(escrow_tx_json_data);
auto lo_ptr = wasmAlloc(escrow_lo_json_data);
if (tx_ptr == 0 || lo_ptr == 0)
{
printf("data error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
auto txLen = (int32_t)escrow_tx_json_data.size();
auto loLen = (int32_t)escrow_lo_json_data.size();
WasmEdge_Value params[4] = {
WasmEdge_ValueGenI32(tx_ptr),
WasmEdge_ValueGenI32(txLen),
WasmEdge_ValueGenI32(lo_ptr),
WasmEdge_ValueGenI32(loLen)};
WasmEdge_Value returns[1];
WasmEdge_String func = WasmEdge_StringCreateByCString(funcName.c_str());
WasmEdge_Result funcRes =
WasmEdge_VMExecute(VMCxt, func, params, 4, returns, 1);
if (WasmEdge_ResultOK(funcRes))
{
auto pointer = WasmEdge_ValueGetI32(returns[0]);
const WasmEdge_ModuleInstanceContext* m =
WasmEdge_VMGetActiveModule(VMCxt);
WasmEdge_String mName = WasmEdge_StringCreateByCString("memory");
WasmEdge_MemoryInstanceContext* mi =
WasmEdge_ModuleInstanceFindMemory(m, mName);
uint8_t buff[9];
WasmEdge_Result getRes =
WasmEdge_MemoryInstanceGetData(mi, buff, pointer, 9);
if (!WasmEdge_ResultOK(getRes))
{
printf(
"re mem get message: %s\n", WasmEdge_ResultGetMessage(getRes));
return Unexpected<TER>(tecFAILED_PROCESSING);
}
auto flag = buff[0];
auto leToInt32 = [](const uint8_t* d) -> uint32_t {
uint32_t r = 0;
for (int i = 0; i < 4; ++i)
{
r |= static_cast<uint32_t>(d[i]) << (i * 8);
// printf("leToInt32 %d\n", r);
}
return r;
};
auto ret_pointer =
leToInt32(reinterpret_cast<const uint8_t*>(&buff[1]));
auto ret_len = leToInt32(reinterpret_cast<const uint8_t*>(&buff[5]));
// printf("re flag %d, ptr %d, len %d\n", flag, ret_pointer,
// ret_len);
std::vector<uint8_t> buff2(ret_len);
getRes = WasmEdge_MemoryInstanceGetData(
mi, buff2.data(), ret_pointer, ret_len);
if (!WasmEdge_ResultOK(getRes))
{
printf(
"re 2 mem get message: %s\n",
WasmEdge_ResultGetMessage(getRes));
return Unexpected<TER>(tecFAILED_PROCESSING);
}
std::string newData(buff2.begin(), buff2.end());
// free
WasmEdge_String freeFunc = WasmEdge_StringCreateByCString("deallocate");
WasmEdge_Value freeParams[2] = {
WasmEdge_ValueGenI32(ret_pointer), WasmEdge_ValueGenI32(ret_len)};
WasmEdge_Value freeReturns[0];
WasmEdge_VMExecute(VMCxt, freeFunc, freeParams, 2, freeReturns, 0);
// free pointer too, with len = 9 too
freeParams[0] = WasmEdge_ValueGenI32(pointer);
freeParams[1] = WasmEdge_ValueGenI32(9);
WasmEdge_VMExecute(VMCxt, freeFunc, freeParams, 2, freeReturns, 0);
return std::pair<bool, std::string>(flag == 1, newData);
}
else
{
printf("Func message: %s\n", WasmEdge_ResultGetMessage(funcRes));
return Unexpected<TER>(tecFAILED_PROCESSING);
}
}
WasmEdge_Result
get_ledger_sqn(
void* data,
const WasmEdge_CallingFrameContext*,
const WasmEdge_Value* In,
WasmEdge_Value* Out)
{
Out[0] =
WasmEdge_ValueGenI32(((LedgerDataProvider*)data)->get_ledger_sqn());
return WasmEdge_Result_Success;
}
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
LedgerDataProvider* ledgerDataProvider)
{
WasmEdge_VMContext* VMCxt = WasmEdge_VMCreate(NULL, NULL);
{ // register host function
WasmEdge_ValType ReturnList[1] = {WasmEdge_ValTypeGenI32()};
WasmEdge_FunctionTypeContext* HostFType =
WasmEdge_FunctionTypeCreate(NULL, 0, ReturnList, 1);
WasmEdge_FunctionInstanceContext* HostFunc =
WasmEdge_FunctionInstanceCreate(
HostFType, get_ledger_sqn, ledgerDataProvider, 0);
WasmEdge_FunctionTypeDelete(HostFType);
WasmEdge_String HostName = WasmEdge_StringCreateByCString("host_lib");
WasmEdge_ModuleInstanceContext* HostMod =
WasmEdge_ModuleInstanceCreate(HostName);
WasmEdge_StringDelete(HostName);
WasmEdge_String HostFuncName =
WasmEdge_StringCreateByCString("get_ledger_sqn");
WasmEdge_ModuleInstanceAddFunction(HostMod, HostFuncName, HostFunc);
WasmEdge_StringDelete(HostFuncName);
WasmEdge_Result regRe =
WasmEdge_VMRegisterModuleFromImport(VMCxt, HostMod);
if (!WasmEdge_ResultOK(regRe))
{
printf("host func reg error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
}
WasmEdge_Result loadRes =
WasmEdge_VMLoadWasmFromBuffer(VMCxt, wasmCode.data(), wasmCode.size());
if (!WasmEdge_ResultOK(loadRes))
{
printf("load error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Result validateRes = WasmEdge_VMValidate(VMCxt);
if (!WasmEdge_ResultOK(validateRes))
{
printf("validate error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Result instantiateRes = WasmEdge_VMInstantiate(VMCxt);
if (!WasmEdge_ResultOK(instantiateRes))
{
printf("instantiate error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Value funcReturns[1];
WasmEdge_String func = WasmEdge_StringCreateByCString(funcName.c_str());
WasmEdge_Result funcRes =
WasmEdge_VMExecute(VMCxt, func, NULL, 0, funcReturns, 1);
bool ok = WasmEdge_ResultOK(funcRes);
bool re = false;
if (ok)
{
auto result = WasmEdge_ValueGetI32(funcReturns[0]);
if (result != 0)
re = true;
}
else
{
printf("Error message: %s\n", WasmEdge_ResultGetMessage(funcRes));
}
WasmEdge_VMDelete(VMCxt);
WasmEdge_StringDelete(func);
if (ok)
return re;
else
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Result
constInt(
void* data,
const WasmEdge_CallingFrameContext*,
const WasmEdge_Value* In,
WasmEdge_Value* Out)
{
Out[0] = WasmEdge_ValueGenI32(5);
return WasmEdge_Result_Success;
}
Expected<EscrowResultP6, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
int32_t gasLimit,
int32_t input)
{
WasmEdge_ConfigureContext* conf = WasmEdge_ConfigureCreate();
WasmEdge_ConfigureStatisticsSetInstructionCounting(conf, true);
WasmEdge_ConfigureStatisticsSetCostMeasuring(conf, true);
WasmEdge_VMContext* VMCxt = WasmEdge_VMCreate(conf, NULL);
WasmEdge_StatisticsContext* StatCxt =
WasmEdge_VMGetStatisticsContext(VMCxt);
WasmEdge_StatisticsSetCostLimit(StatCxt, gasLimit);
{ // register host function
WasmEdge_ValType ReturnList[1] = {WasmEdge_ValTypeGenI32()};
WasmEdge_FunctionTypeContext* HostFType =
WasmEdge_FunctionTypeCreate(NULL, 0, ReturnList, 1);
WasmEdge_FunctionInstanceContext* HostFunc =
WasmEdge_FunctionInstanceCreate(HostFType, constInt, nullptr, 100);
WasmEdge_FunctionTypeDelete(HostFType);
WasmEdge_String HostName = WasmEdge_StringCreateByCString("host_lib");
WasmEdge_ModuleInstanceContext* HostMod =
WasmEdge_ModuleInstanceCreate(HostName);
WasmEdge_StringDelete(HostName);
WasmEdge_String HostFuncName =
WasmEdge_StringCreateByCString("constInt");
WasmEdge_ModuleInstanceAddFunction(HostMod, HostFuncName, HostFunc);
WasmEdge_StringDelete(HostFuncName);
WasmEdge_Result regRe =
WasmEdge_VMRegisterModuleFromImport(VMCxt, HostMod);
if (!WasmEdge_ResultOK(regRe))
{
printf("host func reg error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
}
WasmEdge_Result loadRes =
WasmEdge_VMLoadWasmFromBuffer(VMCxt, wasmCode.data(), wasmCode.size());
if (!WasmEdge_ResultOK(loadRes))
{
printf("load error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Result validateRes = WasmEdge_VMValidate(VMCxt);
if (!WasmEdge_ResultOK(validateRes))
{
printf("validate error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Result instantiateRes = WasmEdge_VMInstantiate(VMCxt);
if (!WasmEdge_ResultOK(instantiateRes))
{
printf("instantiate error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_String func = WasmEdge_StringCreateByCString(funcName.c_str());
WasmEdge_Value Params[1] = {WasmEdge_ValueGenI32(input)};
WasmEdge_Result funcRes =
WasmEdge_VMExecute(VMCxt, func, Params, 1, NULL, 0);
bool ok = WasmEdge_ResultOK(funcRes);
EscrowResultP6 re;
if (ok)
{
auto sc = WasmEdge_VMGetStatisticsContext(VMCxt);
re.cost = WasmEdge_StatisticsGetTotalCost(sc);
// WasmEdge_StatisticsGetTotalCost, WasmEdge_StatisticsGetInstrCount
re.result = true;
}
else
{
printf("Error message: %s\n", WasmEdge_ResultGetMessage(funcRes));
}
WasmEdge_VMDelete(VMCxt);
WasmEdge_StringDelete(func);
// delete other obj allocated
if (ok)
return re;
else
return Unexpected<TER>(tecFAILED_PROCESSING);
}
} // namespace ripple

View File

@@ -0,0 +1,97 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_APP_MISC_WASMVM_H_INLCUDED
#define RIPPLE_APP_MISC_WASMVM_H_INLCUDED
#include <xrpl/basics/Expected.h>
// #include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/TER.h>
#include <wasmedge/wasmedge.h>
namespace ripple {
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
int32_t input);
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
std::vector<uint8_t> const& accountID);
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
std::vector<uint8_t> const& escrow_tx_json_data,
std::vector<uint8_t> const& escrow_lo_json_data);
Expected<std::pair<bool, std::string>, TER>
runEscrowWasmP4(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
std::vector<uint8_t> const& escrow_tx_json_data,
std::vector<uint8_t> const& escrow_lo_json_data);
struct LedgerDataProvider
{
virtual int32_t
get_ledger_sqn()
{
return 1;
}
virtual ~LedgerDataProvider() = default;
};
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
LedgerDataProvider* ledgerDataProvider);
struct EscrowResultP6
{
bool result;
int32_t cost;
};
Expected<EscrowResultP6, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
int32_t gasLimit,
int32_t input);
} // namespace ripple
#endif // RIPPLE_APP_MISC_WASMVM_H_INLCUDED
// class WasmVM final
//{
// public:
// explicit WasmVM(beast::Journal j);
// ~WasmVM() = default;
//
// private:
// beast::Journal j_;
// };

View File

@@ -128,6 +128,18 @@ Change::preclaim(PreclaimContext const& ctx)
ctx.tx.isFieldPresent(sfReserveIncrementDrops)) ctx.tx.isFieldPresent(sfReserveIncrementDrops))
return temDISABLED; return temDISABLED;
} }
if (ctx.view.rules().enabled(featureSmartEscrow))
{
if (!ctx.tx.isFieldPresent(sfExtensionComputeLimit) ||
!ctx.tx.isFieldPresent(sfExtensionSizeLimit))
return temMALFORMED;
}
else
{
if (ctx.tx.isFieldPresent(sfExtensionComputeLimit) ||
ctx.tx.isFieldPresent(sfExtensionSizeLimit))
return temDISABLED;
}
return tesSUCCESS; return tesSUCCESS;
case ttAMENDMENT: case ttAMENDMENT:
case ttUNL_MODIFY: case ttUNL_MODIFY:
@@ -375,6 +387,11 @@ Change::applyFee()
set(feeObject, ctx_.tx, sfReserveBase); set(feeObject, ctx_.tx, sfReserveBase);
set(feeObject, ctx_.tx, sfReserveIncrement); set(feeObject, ctx_.tx, sfReserveIncrement);
} }
if (view().rules().enabled(featureSmartEscrow))
{
set(feeObject, ctx_.tx, sfExtensionComputeLimit);
set(feeObject, ctx_.tx, sfExtensionSizeLimit);
}
view().update(feeObject); view().update(feeObject);

View File

@@ -21,6 +21,7 @@
#include <xrpld/app/misc/CredentialHelpers.h> #include <xrpld/app/misc/CredentialHelpers.h>
#include <xrpld/app/misc/HashRouter.h> #include <xrpld/app/misc/HashRouter.h>
#include <xrpld/app/misc/WasmVM.h>
#include <xrpld/conditions/Condition.h> #include <xrpld/conditions/Condition.h>
#include <xrpld/conditions/Fulfillment.h> #include <xrpld/conditions/Fulfillment.h>
#include <xrpld/ledger/ApplyView.h> #include <xrpld/ledger/ApplyView.h>
@@ -97,9 +98,28 @@ EscrowCreate::makeTxConsequences(PreflightContext const& ctx)
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()}; return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
} }
XRPAmount
EscrowCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
{
XRPAmount txnFees{Transactor::calculateBaseFee(view, tx)};
if (tx.isFieldPresent(sfFinishFunction))
{
// TODO: make this fee increase based on the extra compute run
txnFees += 1000;
}
return txnFees;
}
NotTEC NotTEC
EscrowCreate::preflight(PreflightContext const& ctx) EscrowCreate::preflight(PreflightContext const& ctx)
{ {
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
!ctx.rules.enabled(featureSmartEscrow))
{
JLOG(ctx.j.debug()) << "SmartEscrow not enabled";
return temDISABLED;
}
if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask) if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
return temINVALID_FLAG; return temINVALID_FLAG;
@@ -122,14 +142,23 @@ EscrowCreate::preflight(PreflightContext const& ctx)
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter]) ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
return temBAD_EXPIRATION; return temBAD_EXPIRATION;
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
!ctx.tx.isFieldPresent(sfCancelAfter))
return temBAD_EXPIRATION;
if (ctx.rules.enabled(fix1571)) if (ctx.rules.enabled(fix1571))
{ {
// In the absence of a FinishAfter, the escrow can be finished // In the absence of a FinishAfter, the escrow can be finished
// immediately, which can be confusing. When creating an escrow, // immediately, which can be confusing. When creating an escrow,
// we want to ensure that either a FinishAfter time is explicitly // we want to ensure that either a FinishAfter time is explicitly
// specified or a completion condition is attached. // specified or a completion condition is attached.
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition]) if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition] &&
!ctx.tx[~sfFinishFunction])
{
JLOG(ctx.j.debug()) << "Must have at least one of FinishAfter, "
"Condition, or FinishFunction.";
return temMALFORMED; return temMALFORMED;
}
} }
if (auto const cb = ctx.tx[~sfCondition]) if (auto const cb = ctx.tx[~sfCondition])
@@ -154,6 +183,19 @@ EscrowCreate::preflight(PreflightContext const& ctx)
return temDISABLED; return temDISABLED;
} }
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;
}
// TODO: add check to ensure this is valid WASM code
}
return preflight2(ctx); return preflight2(ctx);
} }
@@ -254,6 +296,8 @@ EscrowCreate::doApply()
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter]; (*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter]; (*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag]; (*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
(*slep)[~sfFinishFunction] = ctx_.tx[~sfFinishFunction];
(*slep)[~sfData] = ctx_.tx[~sfData];
ctx_.view().insert(slep); ctx_.view().insert(slep);
@@ -277,8 +321,13 @@ EscrowCreate::doApply()
} }
// Deduct owner's balance, increment owner count // Deduct owner's balance, increment owner count
// TODO: determine actual reserve based on FinishFunction size
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount]; (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); adjustOwnerCount(
ctx_.view(),
sle,
ctx_.tx.isFieldPresent(sfFinishFunction) ? 2 : 1,
ctx_.journal);
ctx_.view().update(sle); ctx_.view().update(sle);
return tesSUCCESS; return tesSUCCESS;
@@ -323,7 +372,10 @@ EscrowFinish::preflight(PreflightContext const& ctx)
// If you specify a condition, then you must also specify // If you specify a condition, then you must also specify
// a fulfillment. // a fulfillment.
if (static_cast<bool>(cb) != static_cast<bool>(fb)) if (static_cast<bool>(cb) != static_cast<bool>(fb))
{
JLOG(ctx.j.debug()) << "Condition != Fulfillment";
return temMALFORMED; return temMALFORMED;
}
// Verify the transaction signature. If it doesn't work // Verify the transaction signature. If it doesn't work
// then don't do any more work. // then don't do any more work.
@@ -367,6 +419,7 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
{ {
extraFee += view.fees().base * (32 + (fb->size() / 16)); extraFee += view.fees().base * (32 + (fb->size() / 16));
} }
// TODO: make this fee increase based on the extra compute run
return Transactor::calculateBaseFee(view, tx) + extraFee; return Transactor::calculateBaseFee(view, tx) + extraFee;
} }
@@ -384,6 +437,22 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
return tesSUCCESS; return tesSUCCESS;
} }
struct EscrowLedgerDataProvider : public LedgerDataProvider
{
ApplyView& view_;
public:
EscrowLedgerDataProvider(ApplyView& view) : view_(view)
{
}
int32_t
get_ledger_sqn() override
{
return (int32_t)view_.seq();
}
};
TER TER
EscrowFinish::doApply() EscrowFinish::doApply()
{ {
@@ -392,6 +461,13 @@ EscrowFinish::doApply()
if (!slep) if (!slep)
return tecNO_TARGET; return tecNO_TARGET;
// Order of processing the release conditions (in order of performance):
// FinishAfter/CancelAfter
// Destination validity (after SmartEscrow is enabled)
// Condition/Fulfillment
// Destination validity (before SmartEscrow is enabled)
// FinishFunction
// If a cancel time is present, a finish operation should only succeed prior // If a cancel time is present, a finish operation should only succeed prior
// to that time. fix1571 corrects a logic error in the check that would make // to that time. fix1571 corrects a logic error in the check that would make
// a finish only succeed strictly after the cancel time. // a finish only succeed strictly after the cancel time.
@@ -401,11 +477,17 @@ EscrowFinish::doApply()
// Too soon: can't execute before the finish time // Too soon: can't execute before the finish time
if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter])) if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
{
JLOG(j_.debug()) << "Too soon";
return tecNO_PERMISSION; return tecNO_PERMISSION;
}
// Too late: can't execute after the cancel time // Too late: can't execute after the cancel time
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter])) if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
{
JLOG(j_.debug()) << "Too late";
return tecNO_PERMISSION; return tecNO_PERMISSION;
}
} }
else else
{ {
@@ -413,13 +495,35 @@ EscrowFinish::doApply()
if ((*slep)[~sfFinishAfter] && if ((*slep)[~sfFinishAfter] &&
ctx_.view().info().parentCloseTime.time_since_epoch().count() <= ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
(*slep)[sfFinishAfter]) (*slep)[sfFinishAfter])
{
JLOG(j_.debug()) << "Too soon?";
return tecNO_PERMISSION; return tecNO_PERMISSION;
}
// Too late? // Too late?
if ((*slep)[~sfCancelAfter] && if ((*slep)[~sfCancelAfter] &&
ctx_.view().info().parentCloseTime.time_since_epoch().count() <= ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
(*slep)[sfCancelAfter]) (*slep)[sfCancelAfter])
{
JLOG(j_.debug()) << "Too late?";
return tecNO_PERMISSION; 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 (ctx_.view().rules().enabled(featureDepositAuth))
{
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
!isTesSuccess(err))
return err;
}
} }
// Check cryptocondition fulfillment // Check cryptocondition fulfillment
@@ -469,17 +573,56 @@ EscrowFinish::doApply()
return tecCRYPTOCONDITION_ERROR; return tecCRYPTOCONDITION_ERROR;
} }
// NOTE: Escrow payments cannot be used to fund accounts. if (!ctx_.view().rules().enabled(featureSmartEscrow))
AccountID const destID = (*slep)[sfDestination];
auto const sled = ctx_.view().peek(keylet::account(destID));
if (!sled)
return tecNO_DST;
if (ctx_.view().rules().enabled(featureDepositAuth))
{ {
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled); // NOTE: Escrow payments cannot be used to fund accounts.
!isTesSuccess(err)) if (!sled)
return err; return tecNO_DST;
if (ctx_.view().rules().enabled(featureDepositAuth))
{
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
!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());
std::string funcName("ready");
auto const escrowTx =
ctx_.tx.getJson(JsonOptions::none).toStyledString();
auto const escrowObj =
slep->getJson(JsonOptions::none).toStyledString();
std::vector<uint8_t> escrowTxData(escrowTx.begin(), escrowTx.end());
std::vector<uint8_t> escrowObjData(escrowObj.begin(), escrowObj.end());
EscrowLedgerDataProvider ledgerDataProvider(ctx_.view());
auto re = runEscrowWasm(wasm, funcName, &ledgerDataProvider);
JLOG(j_.trace()) << "Escrow WASM ran";
if (re.has_value())
{
auto reValue = re.value();
JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue);
if (!reValue)
{
// ctx_.view().update(slep);
return tecWASM_REJECTED;
}
}
else
{
JLOG(j_.debug()) << "WASM Failure: " + transHuman(re.error());
return re.error();
}
} }
AccountID const account = (*slep)[sfAccount]; AccountID const account = (*slep)[sfAccount];
@@ -512,7 +655,11 @@ EscrowFinish::doApply()
// Adjust source owner count // Adjust source owner count
auto const sle = ctx_.view().peek(keylet::account(account)); auto const sle = ctx_.view().peek(keylet::account(account));
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); adjustOwnerCount(
ctx_.view(),
sle,
slep->isFieldPresent(sfFinishFunction) ? -2 : -1,
ctx_.journal);
ctx_.view().update(sle); ctx_.view().update(sle);
// Remove escrow from ledger // Remove escrow from ledger
@@ -594,7 +741,11 @@ EscrowCancel::doApply()
// Transfer amount back to owner, decrement owner count // Transfer amount back to owner, decrement owner count
auto const sle = ctx_.view().peek(keylet::account(account)); auto const sle = ctx_.view().peek(keylet::account(account));
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount]; (*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount];
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); adjustOwnerCount(
ctx_.view(),
sle,
slep->isFieldPresent(sfFinishFunction) ? -2 : -1,
ctx_.journal);
ctx_.view().update(sle); ctx_.view().update(sle);
// Remove escrow from ledger // Remove escrow from ledger

View File

@@ -36,6 +36,9 @@ public:
static TxConsequences static TxConsequences
makeTxConsequences(PreflightContext const& ctx); makeTxConsequences(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static NotTEC static NotTEC
preflight(PreflightContext const& ctx); preflight(PreflightContext const& ctx);

View File

@@ -831,6 +831,22 @@ 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 static void
removeDeletedTrustLines( removeDeletedTrustLines(
ApplyView& view, ApplyView& view,
@@ -973,6 +989,7 @@ Transactor::operator()()
else if ( else if (
(result == tecOVERSIZE) || (result == tecKILLED) || (result == tecOVERSIZE) || (result == tecKILLED) ||
(result == tecINCOMPLETE) || (result == tecEXPIRED) || (result == tecINCOMPLETE) || (result == tecEXPIRED) ||
(result == tecWASM_REJECTED) ||
(isTecClaimHardFail(result, view().flags()))) (isTecClaimHardFail(result, view().flags())))
{ {
JLOG(j_.trace()) << "reapplying because of " << transToken(result); JLOG(j_.trace()) << "reapplying because of " << transToken(result);
@@ -985,13 +1002,16 @@ Transactor::operator()()
std::vector<uint256> removedTrustLines; std::vector<uint256> removedTrustLines;
std::vector<uint256> expiredNFTokenOffers; std::vector<uint256> expiredNFTokenOffers;
std::vector<uint256> expiredCredentials; std::vector<uint256> expiredCredentials;
std::vector<std::pair<uint256, Blob>> modifiedWasmObjects;
bool const doOffers = bool const doOffers =
((result == tecOVERSIZE) || (result == tecKILLED)); ((result == tecOVERSIZE) || (result == tecKILLED));
bool const doLines = (result == tecINCOMPLETE); bool const doLines = (result == tecINCOMPLETE);
bool const doNFTokenOffers = (result == tecEXPIRED); bool const doNFTokenOffers = (result == tecEXPIRED);
bool const doCredentials = (result == tecEXPIRED); bool const doCredentials = (result == tecEXPIRED);
if (doOffers || doLines || doNFTokenOffers || doCredentials) bool const doWasmData = (result == tecWASM_REJECTED);
if (doOffers || doLines || doNFTokenOffers || doCredentials ||
doWasmData)
{ {
ctx_.visit([doOffers, ctx_.visit([doOffers,
&removedOffers, &removedOffers,
@@ -1000,7 +1020,9 @@ Transactor::operator()()
doNFTokenOffers, doNFTokenOffers,
&expiredNFTokenOffers, &expiredNFTokenOffers,
doCredentials, doCredentials,
&expiredCredentials]( &expiredCredentials,
doWasmData,
&modifiedWasmObjects](
uint256 const& index, uint256 const& index,
bool isDelete, bool isDelete,
std::shared_ptr<SLE const> const& before, std::shared_ptr<SLE const> const& before,
@@ -1035,6 +1057,13 @@ Transactor::operator()()
(before->getType() == ltCREDENTIAL)) (before->getType() == ltCREDENTIAL))
expiredCredentials.push_back(index); expiredCredentials.push_back(index);
} }
if (doWasmData && before && after &&
(before->getType() == ltESCROW))
{
modifiedWasmObjects.push_back(
std::make_pair(index, after->getFieldVL(sfData)));
}
}); });
} }
@@ -1064,6 +1093,10 @@ Transactor::operator()()
removeExpiredCredentials( removeExpiredCredentials(
view(), expiredCredentials, ctx_.app.journal("View")); view(), expiredCredentials, ctx_.app.journal("View"));
if (result == tecWASM_REJECTED)
modifyWasmDataFields(
view(), modifiedWasmObjects, ctx_.app.journal("View"));
applied = isTecClaim(result); applied = isTecClaim(result);
} }

View File

@@ -80,6 +80,12 @@ struct FeeSetup
/** The per-owned item reserve requirement in drops. */ /** The per-owned item reserve requirement in drops. */
XRPAmount owner_reserve{2 * DROPS_PER_XRP}; XRPAmount owner_reserve{2 * DROPS_PER_XRP};
/** The compute limit for Feature Extensions. */
std::uint32_t extension_compute_limit{4294967295};
/** The WASM size limit for Feature Extensions. */
std::uint32_t extension_size_limit{4294967295};
/* (Remember to update the example cfg files when changing any of these /* (Remember to update the example cfg files when changing any of these
* values.) */ * values.) */
}; };

View File

@@ -1102,6 +1102,10 @@ setup_FeeVote(Section const& section)
setup.account_reserve = temp; setup.account_reserve = temp;
if (set(temp, "owner_reserve", section)) if (set(temp, "owner_reserve", section))
setup.owner_reserve = temp; setup.owner_reserve = temp;
if (set(temp, "extension_compute_limit", section))
setup.extension_compute_limit = temp;
if (set(temp, "extension_size_limit", section))
setup.extension_size_limit = temp;
} }
return setup; return setup;
} }