mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-28 17:50:55 +00:00
Compare commits
191 Commits
ripple/se/
...
supported_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3c8dbb46f | ||
|
|
5ae09518b8 | ||
|
|
ef438aec26 | ||
|
|
5fd5839f2b | ||
|
|
75fac65e27 | ||
|
|
ecae6b6346 | ||
|
|
6f4fe178dd | ||
|
|
083986ab82 | ||
|
|
c145201538 | ||
|
|
4fbbd6ff59 | ||
|
|
39c95fee3b | ||
|
|
a41889df31 | ||
|
|
62d9f40d33 | ||
|
|
d3550fac60 | ||
|
|
10ef15fff1 | ||
|
|
2307cfdc5a | ||
|
|
3b328cfc45 | ||
|
|
e20b2430ba | ||
|
|
99072fbddf | ||
|
|
059cefa35a | ||
|
|
a2205190a6 | ||
|
|
4c657bc92e | ||
|
|
ac481a0bd7 | ||
|
|
7eacd9d735 | ||
|
|
d562129e7c | ||
|
|
74beb5f80d | ||
|
|
4bae4d781f | ||
|
|
01a5e07830 | ||
|
|
fe2e5ce649 | ||
|
|
6afa51142d | ||
|
|
0acfa1c4f2 | ||
|
|
309fdfc3f2 | ||
|
|
f8a149c675 | ||
|
|
780380da7e | ||
|
|
fdfdf4fceb | ||
|
|
a76bd834b6 | ||
|
|
50a74b899d | ||
|
|
446ad36cbb | ||
|
|
fd14054f17 | ||
|
|
4318b2ebf7 | ||
|
|
e6ee492822 | ||
|
|
d4510147d1 | ||
|
|
a9a94fbf1a | ||
|
|
719ba392db | ||
|
|
f01ac563a9 | ||
|
|
a1844086d7 | ||
|
|
ac173b6827 | ||
|
|
430696682d | ||
|
|
57d2a91ad5 | ||
|
|
94b35a234e | ||
|
|
b5d0078927 | ||
|
|
9538e9b34c | ||
|
|
fb97f7b596 | ||
|
|
845c503ea6 | ||
|
|
ff39fa59d9 | ||
|
|
8015088340 | ||
|
|
61b2fe4f64 | ||
|
|
7ee964f514 | ||
|
|
6ffbef09c2 | ||
|
|
e05f907788 | ||
|
|
e916416642 | ||
|
|
6a54ed7f14 | ||
|
|
1e0741690d | ||
|
|
c5d178f152 | ||
|
|
5a17940e2a | ||
|
|
abfcc4ef67 | ||
|
|
dba187f8c5 | ||
|
|
8f2f8d53b4 | ||
|
|
49acc61961 | ||
|
|
def7758a23 | ||
|
|
58e5b4ad25 | ||
|
|
fa8aa49376 | ||
|
|
a891b49c67 | ||
|
|
7e2e10f02c | ||
|
|
22ca691e75 | ||
|
|
fc6ff69752 | ||
|
|
079e251aca | ||
|
|
b6bd268be2 | ||
|
|
566b85b3d6 | ||
|
|
7d22fe804d | ||
|
|
9cfb7ac340 | ||
|
|
3a0e9aab4f | ||
|
|
3b6cd22e32 | ||
|
|
c9c35780d2 | ||
|
|
17f401f374 | ||
|
|
c6c54b3282 | ||
|
|
91455b6860 | ||
|
|
f16f243c22 | ||
|
|
e41f6a71b7 | ||
|
|
db263b696c | ||
|
|
c41e52f57a | ||
|
|
55772a0d07 | ||
|
|
965a9e89ac | ||
|
|
8d266d3941 | ||
|
|
a865b4da1c | ||
|
|
8729688feb | ||
|
|
f1f798bb85 | ||
|
|
85bff20ae5 | ||
|
|
e6592e93a9 | ||
|
|
2f869b3cfc | ||
|
|
ffa21c27a7 | ||
|
|
5c480cf883 | ||
|
|
adc64e7866 | ||
|
|
4d4a1cfe82 | ||
|
|
f2c7da3705 | ||
|
|
3ab0a82cd3 | ||
|
|
a46d772147 | ||
|
|
f3c50318e8 | ||
|
|
e7aa924c0e | ||
|
|
5266f04970 | ||
|
|
db957cf191 | ||
|
|
8ac514363d | ||
|
|
c2ea68cca4 | ||
|
|
3d86881ce7 | ||
|
|
697d1470f4 | ||
|
|
0b5f8f4051 | ||
|
|
0fed78fbcc | ||
|
|
8c38ef726b | ||
|
|
2399d90334 | ||
|
|
6367d68d1e | ||
|
|
155a84c8a3 | ||
|
|
10558c9eff | ||
|
|
dd30d811e6 | ||
|
|
293d8e4ddb | ||
|
|
77875c9133 | ||
|
|
647b47567e | ||
|
|
b0a1ad3b06 | ||
|
|
1d141bf2e8 | ||
|
|
0d0e279ae2 | ||
|
|
5dc0cee28a | ||
|
|
c15947da56 | ||
|
|
9bc04244e7 | ||
|
|
38c7a27010 | ||
|
|
58741d2791 | ||
|
|
8426470506 | ||
|
|
ccc3280b1a | ||
|
|
2847075705 | ||
|
|
3108ca0549 | ||
|
|
3b849ff497 | ||
|
|
66776b6a85 | ||
|
|
c8c241b50d | ||
|
|
44cb588371 | ||
|
|
6f91b8f8d1 | ||
|
|
3d93379132 | ||
|
|
84fd7d0126 | ||
|
|
7f52287aae | ||
|
|
98b8986868 | ||
|
|
250f2842ee | ||
|
|
9eca1a3a0c | ||
|
|
24b7a03224 | ||
|
|
9007097d24 | ||
|
|
bc445ec6a2 | ||
|
|
4fa0ae521e | ||
|
|
7bdf5fa8b8 | ||
|
|
65b0b976d9 | ||
|
|
a0d275feec | ||
|
|
ece3a8d7be | ||
|
|
463acf51b5 | ||
|
|
1cd16fab87 | ||
|
|
add55c4f33 | ||
|
|
51a9c0ff59 | ||
|
|
6e8a5f0f4e | ||
|
|
8a33702f26 | ||
|
|
a072d49802 | ||
|
|
a0aeeb8e07 | ||
|
|
383b225690 | ||
|
|
ace2247800 | ||
|
|
6a6fed5dce | ||
|
|
1f8aece8cd | ||
|
|
6c6f8cd4f9 | ||
|
|
fb1311e013 | ||
|
|
ce31acf030 | ||
|
|
31ad5ac63b | ||
|
|
1ede0bdec4 | ||
|
|
aef32ead2c | ||
|
|
5b43ec7f73 | ||
|
|
1e9ff88a00 | ||
|
|
bb9bb5f5c5 | ||
|
|
c533abd8b6 | ||
|
|
bb9bc764bc | ||
|
|
b4b53a6cb7 | ||
|
|
9c0204906c | ||
|
|
4670b373c1 | ||
|
|
f03b5883bd | ||
|
|
f8b2fe4dd5 | ||
|
|
be4a0c9c2b | ||
|
|
f37d52d8e9 | ||
|
|
177cdaf550 | ||
|
|
1573a443b7 | ||
|
|
911c0466c0 | ||
|
|
b6a95f9970 |
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/basics/TaggedCache.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
@@ -246,6 +247,9 @@ public:
|
||||
virtual DatabaseCon&
|
||||
getWalletDB() = 0;
|
||||
|
||||
[[nodiscard]] virtual Fees
|
||||
getFees() const = 0;
|
||||
|
||||
// Temporary: Get the underlying Application for functions that haven't
|
||||
// been migrated yet. This should be removed once all code is migrated.
|
||||
virtual Application&
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <xrpl/protocol/TxMeta.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
@@ -68,6 +69,18 @@ public:
|
||||
deliver_ = amount;
|
||||
}
|
||||
|
||||
void
|
||||
setGasUsed(std::optional<std::uint32_t> const gasUsed)
|
||||
{
|
||||
gasUsed_ = gasUsed;
|
||||
}
|
||||
|
||||
void
|
||||
setVMReturnCode(std::int32_t const vmReturnCode)
|
||||
{
|
||||
vmReturnCode_ = vmReturnCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of modified entries
|
||||
*/
|
||||
@@ -88,6 +101,8 @@ public:
|
||||
|
||||
private:
|
||||
std::optional<STAmount> deliver_;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
std::optional<std::int32_t> vmReturnCode_;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
@@ -62,6 +63,8 @@ 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& vmReturnCode,
|
||||
bool isDryRun,
|
||||
beast::Journal j);
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <ValidIssueType T>
|
||||
@@ -272,4 +274,15 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
journal);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static int32_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);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -237,6 +237,12 @@ page(Keylet const& root, std::uint64_t const index = 0) noexcept
|
||||
Keylet
|
||||
escrow(AccountID const& src, SeqProxy const& seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
escrow(uint256 const& key) noexcept
|
||||
{
|
||||
return {ltESCROW, key};
|
||||
}
|
||||
|
||||
/**
|
||||
* A PaymentChannel
|
||||
*/
|
||||
|
||||
@@ -133,6 +133,8 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temBAD_MPT,
|
||||
temBAD_CIPHERTEXT,
|
||||
temBAD_WASM,
|
||||
temINVALID_BYTECODE,
|
||||
temTEMP_DISABLED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -181,6 +183,8 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefINVALID_LEDGER_FIX_TYPE,
|
||||
tefNO_DST_PARTIAL,
|
||||
tefBAD_PATH_COUNT,
|
||||
tefNO_BYTECODE,
|
||||
tefBYTECODE_NOT_INCLUDED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -373,6 +377,7 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecBAD_PROOF = 199,
|
||||
tecNO_SPONSOR_PERMISSION = 200,
|
||||
tecOUT_OF_GAS = 201,
|
||||
tecBYTECODE_REJECTED = 202,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -95,6 +95,12 @@ public:
|
||||
|
||||
if (obj.isFieldPresent(sfParentBatchID))
|
||||
parentBatchID_ = obj.getFieldH256(sfParentBatchID);
|
||||
|
||||
if (obj.isFieldPresent(sfGasUsed))
|
||||
gasUsed_ = obj.getFieldU32(sfGasUsed);
|
||||
|
||||
if (obj.isFieldPresent(sfVMReturnCode))
|
||||
vmReturnCode_ = obj.getFieldI32(sfVMReturnCode);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<STAmount> const&
|
||||
@@ -115,6 +121,30 @@ public:
|
||||
parentBatchID_ = id;
|
||||
}
|
||||
|
||||
void
|
||||
setGasUsed(std::optional<std::uint32_t> const gasUsed)
|
||||
{
|
||||
gasUsed_ = gasUsed;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<std::uint32_t> const&
|
||||
getGasUsed() const
|
||||
{
|
||||
return gasUsed_;
|
||||
}
|
||||
|
||||
void
|
||||
setVMReturnCode(std::optional<std::int32_t> const vmReturnCode)
|
||||
{
|
||||
vmReturnCode_ = vmReturnCode;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<std::int32_t> const&
|
||||
getVMReturnCode() const
|
||||
{
|
||||
return vmReturnCode_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint256 transactionID_;
|
||||
std::uint32_t ledgerSeq_;
|
||||
@@ -123,6 +153,8 @@ private:
|
||||
|
||||
std::optional<STAmount> deliveredAmount_;
|
||||
std::optional<uint256> parentBatchID_;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
std::optional<std::int32_t> vmReturnCode_;
|
||||
|
||||
STArray nodes_;
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FIX (Cleanup3_4_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SmartEscrow, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SmartEscrow, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocolV1_1, Supported::No, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -344,6 +344,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
|
||||
{sfCondition, SoeOptional},
|
||||
{sfCancelAfter, SoeOptional},
|
||||
{sfFinishAfter, SoeOptional},
|
||||
{sfBytecode, SoeOptional},
|
||||
{sfData, SoeOptional},
|
||||
{sfSourceTag, SoeOptional},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfOwnerNode, SoeRequired},
|
||||
|
||||
@@ -123,6 +123,8 @@ TYPED_SFIELD(sfSponsorFlags, UINT32, 74)
|
||||
TYPED_SFIELD(sfGasLimit, UINT32, 75)
|
||||
TYPED_SFIELD(sfBytecodeSizeLimit, UINT32, 76)
|
||||
TYPED_SFIELD(sfGasPrice, UINT32, 77)
|
||||
TYPED_SFIELD(sfGas, UINT32, 78)
|
||||
TYPED_SFIELD(sfGasUsed, UINT32, 79)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -240,9 +242,10 @@ TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15, SField::kSmdNeedsAsset
|
||||
TYPED_SFIELD(sfPeriodicPayment, NUMBER, 16)
|
||||
TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::kSmdNeedsAsset | SField::kSmdDefault)
|
||||
|
||||
// int32
|
||||
// 32-bit signed (common)
|
||||
TYPED_SFIELD(sfLoanScale, INT32, 1)
|
||||
TYPED_SFIELD(sfRemainingOwnerCountDelta, INT32, 2)
|
||||
TYPED_SFIELD(sfVMReturnCode, INT32, 3)
|
||||
|
||||
// currency amount (common)
|
||||
TYPED_SFIELD(sfAmount, AMOUNT, 1)
|
||||
@@ -272,7 +275,7 @@ TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
|
||||
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
|
||||
TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24)
|
||||
|
||||
// currency amount (AMM)
|
||||
// currency amount (more)
|
||||
TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
|
||||
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
|
||||
TYPED_SFIELD(sfEPrice, AMOUNT, 27)
|
||||
@@ -332,6 +335,7 @@ TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43)
|
||||
TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44)
|
||||
TYPED_SFIELD(sfAmountCommitment, VL, 45)
|
||||
TYPED_SFIELD(sfBalanceCommitment, VL, 46)
|
||||
TYPED_SFIELD(sfBytecode, VL, 47)
|
||||
|
||||
// account (common)
|
||||
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
|
||||
|
||||
@@ -50,11 +50,13 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
|
||||
NoPriv,
|
||||
({
|
||||
{sfDestination, SoeRequired},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfAmount, SoeRequired, SoeMptSupported},
|
||||
{sfCondition, SoeOptional},
|
||||
{sfCancelAfter, SoeOptional},
|
||||
{sfFinishAfter, SoeOptional},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfBytecode, SoeOptional},
|
||||
{sfData, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction type completes an existing escrow. */
|
||||
@@ -71,6 +73,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish,
|
||||
{sfFulfillment, SoeOptional},
|
||||
{sfCondition, SoeOptional},
|
||||
{sfCredentialIDs, SoeOptional},
|
||||
{sfGas, SoeOptional},
|
||||
}))
|
||||
|
||||
|
||||
|
||||
@@ -174,6 +174,54 @@ public:
|
||||
return this->sle_->isFieldPresent(sfFinishAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBytecode (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getBytecode() const
|
||||
{
|
||||
if (hasBytecode())
|
||||
return this->sle_->at(sfBytecode);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBytecode is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBytecode() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfBytecode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfData (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getData() const
|
||||
{
|
||||
if (hasData())
|
||||
return this->sle_->at(sfData);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfData is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasData() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSourceTag (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -453,6 +501,28 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBytecode (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowBuilder&
|
||||
setBytecode(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBytecode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfData (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowBuilder&
|
||||
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfData] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSourceTag (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -58,6 +58,32 @@ public:
|
||||
return this->tx_->at(sfDestination);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfDestinationTag (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getDestinationTag() const
|
||||
{
|
||||
if (hasDestinationTag())
|
||||
{
|
||||
return this->tx_->at(sfDestinationTag);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfDestinationTag is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasDestinationTag() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfDestinationTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
@@ -149,29 +175,55 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfDestinationTag (SoeOptional)
|
||||
* @brief Get sfBytecode (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getDestinationTag() const
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getBytecode() const
|
||||
{
|
||||
if (hasDestinationTag())
|
||||
if (hasBytecode())
|
||||
{
|
||||
return this->tx_->at(sfDestinationTag);
|
||||
return this->tx_->at(sfBytecode);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfDestinationTag is present.
|
||||
* @brief Check if sfBytecode is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasDestinationTag() const
|
||||
hasBytecode() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfDestinationTag);
|
||||
return this->tx_->isFieldPresent(sfBytecode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfData (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getData() const
|
||||
{
|
||||
if (hasData())
|
||||
{
|
||||
return this->tx_->at(sfData);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfData is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasData() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfData);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -232,6 +284,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfDestinationTag (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowCreateBuilder&
|
||||
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfDestinationTag] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
@@ -278,13 +341,24 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfDestinationTag (SoeOptional)
|
||||
* @brief Set sfBytecode (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowCreateBuilder&
|
||||
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
setBytecode(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfDestinationTag] = value;
|
||||
object_[sfBytecode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfData (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowCreateBuilder&
|
||||
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfData] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,32 @@ public:
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfCredentialIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGas (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGas() const
|
||||
{
|
||||
if (hasGas())
|
||||
{
|
||||
return this->tx_->at(sfGas);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGas is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGas() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfGas);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -249,6 +275,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGas (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowFinishBuilder&
|
||||
setGas(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGas] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the EscrowFinish wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
@@ -95,6 +96,24 @@ 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
|
||||
setVMReturnCode(std::int32_t const vmReturnCode)
|
||||
{
|
||||
vmReturnCode_ = vmReturnCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discard changes and start fresh.
|
||||
*/
|
||||
@@ -163,6 +182,8 @@ private:
|
||||
|
||||
// The ID of the batch transaction we are executing under, if set.
|
||||
std::optional<uint256 const> parentBatchId_;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
std::optional<std::int32_t> vmReturnCode_;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -20,15 +20,21 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
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);
|
||||
|
||||
static NotTEC
|
||||
preflightSigValidated(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
|
||||
@@ -113,6 +113,8 @@ 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& vmReturnCode,
|
||||
bool isDryRun,
|
||||
beast::Journal j)
|
||||
{
|
||||
@@ -127,6 +129,8 @@ ApplyStateTable::apply(
|
||||
|
||||
meta.setDeliveredAmount(deliver);
|
||||
meta.setParentBatchID(parentBatchId);
|
||||
meta.setGasUsed(gasUsed);
|
||||
meta.setVMReturnCode(vmReturnCode);
|
||||
|
||||
Mods newMod;
|
||||
for (auto& item : items_)
|
||||
|
||||
@@ -30,7 +30,7 @@ ApplyViewImpl::apply(
|
||||
bool isDryRun,
|
||||
beast::Journal j)
|
||||
{
|
||||
return items_.apply(to, tx, ter, deliver_, parentBatchId, isDryRun, j);
|
||||
return items_.apply(to, tx, ter, deliver_, parentBatchId, gasUsed_, vmReturnCode_, isDryRun, j);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
|
||||
@@ -109,6 +109,7 @@ transResults()
|
||||
MAKE_ERROR(tecBAD_PROOF, "Proof cannot be verified"),
|
||||
MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Sponsor has not authorized this transaction."),
|
||||
MAKE_ERROR(tecOUT_OF_GAS, "The WASM code ran out of gas during execution."),
|
||||
MAKE_ERROR(tecBYTECODE_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."),
|
||||
@@ -134,6 +135,8 @@ transResults()
|
||||
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
|
||||
MAKE_ERROR(tefNO_DST_PARTIAL, "Partial payment to create account not allowed."),
|
||||
MAKE_ERROR(tefBAD_PATH_COUNT, "Malformed: Too many paths."),
|
||||
MAKE_ERROR(tefNO_BYTECODE, "There is no WASM code to run, but a WASM-specific field was included."),
|
||||
MAKE_ERROR(tefBYTECODE_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."),
|
||||
@@ -206,6 +209,8 @@ transResults()
|
||||
MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."),
|
||||
MAKE_ERROR(temBAD_CIPHERTEXT, "Malformed: Invalid ciphertext."),
|
||||
MAKE_ERROR(temBAD_WASM, "Malformed: Provided WASM code is invalid."),
|
||||
MAKE_ERROR(temINVALID_BYTECODE, "Malformed: Provided WASM code is invalid."),
|
||||
MAKE_ERROR(temTEMP_DISABLED, "The transaction requires logic that is currently temporarily disabled."),
|
||||
|
||||
MAKE_ERROR(terRETRY, "Retry transaction."),
|
||||
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),
|
||||
|
||||
@@ -194,6 +194,12 @@ TxMeta::getAsObject() const
|
||||
if (parentBatchID_.has_value())
|
||||
metaData.setFieldH256(sfParentBatchID, *parentBatchID_);
|
||||
|
||||
if (gasUsed_.has_value())
|
||||
metaData.setFieldU32(sfGasUsed, *gasUsed_);
|
||||
|
||||
if (vmReturnCode_.has_value())
|
||||
metaData.setFieldI32(sfVMReturnCode, *vmReturnCode_);
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,18 @@ ApplyContext::discard()
|
||||
std::optional<TxMeta>
|
||||
ApplyContext::apply(TER ter)
|
||||
{
|
||||
// tecINTERNAL reports an xrpld bug, not a result: nothing the VM recorded
|
||||
// before we hit it belongs in the metadata.
|
||||
if (ter != tecINTERNAL)
|
||||
{
|
||||
if (vmReturnCode_.has_value())
|
||||
{
|
||||
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) view_ emplaced in constructor
|
||||
view_->setVMReturnCode(*vmReturnCode_);
|
||||
}
|
||||
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) view_ emplaced in constructor
|
||||
view_->setGasUsed(gasUsed_);
|
||||
}
|
||||
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) view_ emplaced in constructor
|
||||
return view_->apply(base_, tx, ter, parentBatchId_, (flags_ & TapDryRun) != 0u, journal);
|
||||
}
|
||||
|
||||
@@ -1284,6 +1284,23 @@ removeDeletedTrustLines(
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
modifyWasmDataFields(
|
||||
ApplyView& view,
|
||||
std::vector<std::pair<uint256, SLE::const_pointer>> const& wasmObjects,
|
||||
beast::Journal viewJ)
|
||||
{
|
||||
for (auto const& [index, after] : wasmObjects)
|
||||
{
|
||||
if (auto const sle = view.peek(keylet::escrow(index)))
|
||||
{
|
||||
auto const data = after->getFieldVL(sfData);
|
||||
sle->setFieldVL(sfData, data);
|
||||
view.update(sle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the context, discarding any changes made and adjust the fee.
|
||||
*
|
||||
@@ -1451,6 +1468,10 @@ Transactor::processPersistentChanges(TER result, XRPAmount fee)
|
||||
types.insert(ltNFTOKEN_OFFER);
|
||||
types.insert(ltCREDENTIAL);
|
||||
}
|
||||
else if (ter == tecBYTECODE_REJECTED)
|
||||
{
|
||||
types.insert(ltESCROW);
|
||||
}
|
||||
return types;
|
||||
};
|
||||
|
||||
@@ -1460,10 +1481,11 @@ Transactor::processPersistentChanges(TER result, XRPAmount fee)
|
||||
auto const typesToCollect = typesForResult(result);
|
||||
|
||||
std::map<LedgerEntryType, std::vector<uint256>> deletedObjects;
|
||||
std::map<LedgerEntryType, std::vector<std::pair<uint256, SLE::const_pointer>>> modifiedObjects;
|
||||
if (!typesToCollect.empty())
|
||||
{
|
||||
ctx_.visit(
|
||||
[&typesToCollect, &deletedObjects](
|
||||
[&typesToCollect, &deletedObjects, &modifiedObjects](
|
||||
uint256 const& index, bool isDelete, SLE::const_ref before, SLE::const_ref after) {
|
||||
if (isDelete)
|
||||
{
|
||||
@@ -1487,6 +1509,16 @@ Transactor::processPersistentChanges(TER result, XRPAmount fee)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (after)
|
||||
{
|
||||
// Collect modified escrows so that data written by a
|
||||
// rejected WASM execution can be re-applied after the
|
||||
// context is reset.
|
||||
auto const type = after->getType();
|
||||
if (typesToCollect.contains(type) && type == ltESCROW &&
|
||||
after->isFieldPresent(sfData))
|
||||
modifiedObjects[type].emplace_back(index, after);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1533,6 +1565,24 @@ Transactor::processPersistentChanges(TER result, XRPAmount fee)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
for (auto const& [type, ids] : modifiedObjects)
|
||||
{
|
||||
if (ids.empty() || !typesToApply.contains(type))
|
||||
continue;
|
||||
switch (type)
|
||||
{
|
||||
case ltESCROW:
|
||||
modifyWasmDataFields(view(), ids, viewJ);
|
||||
break;
|
||||
// LCOV_EXCL_START
|
||||
default:
|
||||
UNREACHABLE(
|
||||
"xrpl::Transactor::processPersistentChanges() : "
|
||||
"unexpected type");
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {result, fee, isTecClaim(result)};
|
||||
@@ -1652,7 +1702,8 @@ Transactor::operator()()
|
||||
}
|
||||
else if (
|
||||
(result == tecOVERSIZE) || (result == tecKILLED) || (result == tecINCOMPLETE) ||
|
||||
(result == tecEXPIRED) || (isTecClaimHardFail(result, view().flags())))
|
||||
(result == tecEXPIRED) || (result == tecBYTECODE_REJECTED) ||
|
||||
(isTecClaimHardFail(result, view().flags())))
|
||||
{
|
||||
std::tie(result, fee, applied) = processPersistentChanges(result, fee);
|
||||
}
|
||||
|
||||
@@ -212,7 +212,8 @@ EscrowCancel::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal);
|
||||
auto const reserveToSubtract = calculateAdditionalReserve((*slep)[~sfBytecode]);
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, slep, reserveToSubtract, ctx_.journal);
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
#include <xrpl/conditions/Condition.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
@@ -32,6 +34,8 @@
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
#include <xrpl/tx/applySteps.h>
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
#include <xrpl/tx/wasm/WasmVM.h>
|
||||
|
||||
#include <memory>
|
||||
#include <system_error>
|
||||
@@ -88,8 +92,13 @@ EscrowCreate::checkExtraFeatures(PreflightContext const& ctx)
|
||||
// Only require featureMPTokensV1 when the escrow amount is an MPT and
|
||||
// fixCleanup3_2_0 is active; XRP/IOU escrows are unaffected by this gate.
|
||||
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfAmount].holds<MPTIssue>())
|
||||
return ctx.rules.enabled(featureMPTokensV1);
|
||||
return true;
|
||||
{
|
||||
if (!ctx.rules.enabled(featureMPTokensV1))
|
||||
return false;
|
||||
}
|
||||
|
||||
return (!ctx.tx.isFieldPresent(sfBytecode) && !ctx.tx.isFieldPresent(sfData)) ||
|
||||
ctx.rules.enabled(featureSmartEscrow);
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
@@ -124,6 +133,19 @@ 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(sfBytecode))
|
||||
{
|
||||
// 10 base fees for the transaction (1 is in
|
||||
// `Transactor::calculateBaseFee`), plus 5 drops per byte
|
||||
txnFees += 9 * view.fees().base + 5 * tx[sfBytecode].size();
|
||||
}
|
||||
return txnFees;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
@@ -155,12 +177,19 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
|
||||
return temBAD_EXPIRATION;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfBytecode) && !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])
|
||||
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition] && !ctx.tx[~sfBytecode])
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Must have at least one of FinishAfter, "
|
||||
"Condition, or Bytecode.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (auto const cb = ctx.tx[~sfCondition])
|
||||
{
|
||||
@@ -176,6 +205,60 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfData))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfBytecode))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "EscrowCreate with Data requires Bytecode";
|
||||
return temMALFORMED;
|
||||
}
|
||||
auto const data = ctx.tx.getFieldVL(sfData);
|
||||
if (data.size() > kMaxWasmDataLength)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "EscrowCreate.Data bad size " << data.size();
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfBytecode))
|
||||
{
|
||||
auto const fees(ctx.registry.get().getFees());
|
||||
if (fees.bytecodeSizeLimit == 0 || fees.gasLimit == 0)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "WASM runtime deactivated by fee voting";
|
||||
return temTEMP_DISABLED;
|
||||
}
|
||||
|
||||
auto const code = ctx.tx.getFieldVL(sfBytecode);
|
||||
if (code.empty() || code.size() > fees.bytecodeSizeLimit)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "EscrowCreate.Bytecode bad size " << code.size();
|
||||
return temMALFORMED;
|
||||
}
|
||||
// actual validity of WASM code happens in `preflightSigValidated`
|
||||
// (after the signature is checked)
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
EscrowCreate::preflightSigValidated(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfBytecode))
|
||||
{
|
||||
auto const code = ctx.tx.getFieldVL(sfBytecode);
|
||||
// basic checks happen in `preflight`
|
||||
|
||||
HostFunctions mock(ctx.j);
|
||||
auto const re = preflightEscrowWasm(code, mock, escrowFunctionName);
|
||||
if (!isTesSuccess(re))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "EscrowCreate.Bytecode bad WASM";
|
||||
return re;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -434,6 +517,7 @@ EscrowCreate::doApply()
|
||||
|
||||
// Check reserve and funds availability
|
||||
STAmount const amount{ctx_.tx[sfAmount]};
|
||||
auto const reserveToAdd = calculateAdditionalReserve(ctx_.tx[~sfBytecode]);
|
||||
|
||||
auto const balance = sle->getFieldAmount(sfBalance).xrp();
|
||||
// First check: whoever is on the hook for the new owner increment
|
||||
@@ -441,8 +525,8 @@ EscrowCreate::doApply()
|
||||
// validates the sponsor's reserve + remaining credit. When
|
||||
// unsponsored this hits the source branch and validates the
|
||||
// source's pre-lock balance against base + (currentOC+1)*increment.
|
||||
if (auto const ret =
|
||||
checkReserve(ctx_.getApplyViewContext(), sle, balance, {.ownerCountDelta = 1}, j_);
|
||||
if (auto const ret = checkReserve(
|
||||
ctx_.getApplyViewContext(), sle, balance, {.ownerCountDelta = reserveToAdd}, j_);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
@@ -486,6 +570,8 @@ EscrowCreate::doApply()
|
||||
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
|
||||
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
(*slep)[~sfBytecode] = ctx_.tx[~sfBytecode];
|
||||
(*slep)[~sfData] = ctx_.tx[~sfData];
|
||||
|
||||
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
||||
{
|
||||
@@ -553,7 +639,7 @@ EscrowCreate::doApply()
|
||||
}
|
||||
|
||||
// increment owner count
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal);
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sle, reserveToAdd, ctx_.journal);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep);
|
||||
ctx_.view().update(sle);
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -30,9 +32,16 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
#include <xrpl/tx/wasm/HostFuncImpl.h>
|
||||
#include <xrpl/tx/wasm/WasmVM.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -65,7 +74,13 @@ checkCondition(Slice f, Slice c)
|
||||
bool
|
||||
EscrowFinish::checkExtraFeatures(PreflightContext const& ctx)
|
||||
{
|
||||
return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials);
|
||||
if (ctx.tx.isFieldPresent(sfCredentialIDs) && !ctx.rules.enabled(featureCredentials))
|
||||
return false;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfGas) && !ctx.rules.enabled(featureSmartEscrow))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -77,7 +92,32 @@ 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;
|
||||
}
|
||||
|
||||
if (auto const allowance = ctx.tx[~sfGas]; allowance)
|
||||
{
|
||||
auto const fees(ctx.registry.get().getFees());
|
||||
if (fees.gasLimit == 0)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "WASM runtime deactivated by fee voting";
|
||||
return temTEMP_DISABLED;
|
||||
}
|
||||
if (*allowance == 0)
|
||||
{
|
||||
return temBAD_LIMIT;
|
||||
}
|
||||
if (*allowance > fees.gasLimit)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Gas too large: " << *allowance;
|
||||
return temBAD_LIMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -111,9 +151,6 @@ EscrowFinish::preflightSigValidated(PreflightContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -126,7 +163,15 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
extraFee += view.fees().base * (32 + (fb->size() / 16));
|
||||
}
|
||||
|
||||
if (std::optional<uint64_t> const allowance = tx[~sfGas]; 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.
|
||||
uint64_t const allowanceFee =
|
||||
(((*allowance) * view.fees().gasPrice) / microDropsPerDrop) + 1;
|
||||
extraFee += allowanceFee;
|
||||
}
|
||||
return Transactor::calculateBaseFee(view, tx) + extraFee;
|
||||
}
|
||||
|
||||
@@ -202,26 +247,51 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow) ||
|
||||
ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
// this check is done in doApply before this amendment is enabled
|
||||
auto const seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]);
|
||||
auto const k = keylet::escrow(ctx.tx[sfOwner], seqProxy);
|
||||
auto const slep = ctx.view.read(k);
|
||||
if (!slep)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const dest = (*slep)[sfDestination];
|
||||
STAmount const amount = (*slep)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
if (ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
if (slep->isFieldPresent(sfBytecode))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfGas))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Bytecode requires Gas";
|
||||
return tefBYTECODE_NOT_INCLUDED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfGas))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Bytecode not present, "
|
||||
"Gas present";
|
||||
return tefNO_BYTECODE;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
@@ -235,7 +305,8 @@ EscrowFinish::doApply()
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
{
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow) ||
|
||||
ctx_.view().rules().enabled(featureSmartEscrow))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
return tecNO_TARGET;
|
||||
@@ -253,6 +324,20 @@ 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(), accountID_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
|
||||
// Check cryptocondition fulfillment
|
||||
{
|
||||
auto const id = ctx_.tx.getTransactionID();
|
||||
@@ -306,16 +391,78 @@ EscrowFinish::doApply()
|
||||
return tecCRYPTOCONDITION_ERROR;
|
||||
}
|
||||
|
||||
// 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 (!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(), accountID_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
if (auto err =
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
|
||||
// Execute custom release function
|
||||
if ((*slep)[~sfBytecode])
|
||||
{
|
||||
JLOG(j_.trace()) << "The escrow has a finish function, running WASM code...";
|
||||
// WASM execution
|
||||
auto const wasmStr = slep->getFieldVL(sfBytecode);
|
||||
std::vector<uint8_t> const wasm(wasmStr.begin(), wasmStr.end());
|
||||
|
||||
WasmHostFunctionsImpl ledgerDataProvider(ctx_, k);
|
||||
|
||||
if (!ctx_.tx.isFieldPresent(sfGas))
|
||||
{
|
||||
// already checked above, this check is just in case
|
||||
return tecINTERNAL;
|
||||
}
|
||||
std::uint32_t const allowance = ctx_.tx[sfGas];
|
||||
auto const re = runEscrowWasm(wasm, ledgerDataProvider, allowance, escrowFunctionName);
|
||||
JLOG(j_.trace()) << "Escrow WASM ran";
|
||||
|
||||
// Gas consumed, reported in the tx metadata whenever the engine has a
|
||||
// trustworthy number: a completed run, out of gas, or a wasm fault.
|
||||
std::optional<std::int64_t> const cost = re.has_value() ? re->cost : re.error().cost;
|
||||
if (cost.has_value())
|
||||
{
|
||||
// The engine cannot spend more than it was given, and pins the cost
|
||||
// to the allowance when it runs out.
|
||||
if (*cost < 0 || *cost > allowance)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
ctx_.setGasUsed(static_cast<std::uint32_t>(*cost));
|
||||
}
|
||||
|
||||
if (!re.has_value())
|
||||
{
|
||||
// No return code, and any data it wrote goes away with the view.
|
||||
JLOG(j_.debug()) << "WASM Failure: " + transHuman(re.error().ter);
|
||||
return re.error().ter;
|
||||
}
|
||||
|
||||
auto const reValue = re->result;
|
||||
JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue) << ", cost: " << re->cost;
|
||||
|
||||
ctx_.setVMReturnCode(reValue);
|
||||
|
||||
// Only matters on a reject, where the escrow survives:
|
||||
// Transactor::processPersistentChanges replays this after the reset.
|
||||
if (auto const& data = ledgerDataProvider.getData(); data.has_value())
|
||||
{
|
||||
if (data->size() > kMaxWasmDataLength)
|
||||
{
|
||||
// should already be checked in the updateData host function
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
slep->setFieldVL(sfData, makeSlice(*data));
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
// 0 or negative is a contract-defined reject code, reported as sfVMReturnCode.
|
||||
if (reValue <= 0)
|
||||
return tecBYTECODE_REJECTED;
|
||||
}
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
|
||||
@@ -343,6 +490,8 @@ EscrowFinish::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
auto const reserveToSubtract = calculateAdditionalReserve((*slep)[~sfBytecode]);
|
||||
|
||||
// With the Sponsor amendment, release the escrow reserve before delivery.
|
||||
// Token delivery can auto-create a destination holding, and the same
|
||||
// sponsor (or the same account, for a self-escrow) may cover both the
|
||||
@@ -351,7 +500,7 @@ EscrowFinish::doApply()
|
||||
// arithmetic for self-escrows and would break consensus if not gated.
|
||||
bool const sponsorEnabled = ctx_.view().rules().enabled(featureSponsor);
|
||||
if (sponsorEnabled)
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, slep, reserveToSubtract, ctx_.journal);
|
||||
|
||||
STAmount const amount = slep->getFieldAmount(sfAmount);
|
||||
// Transfer amount to destination
|
||||
@@ -404,7 +553,7 @@ EscrowFinish::doApply()
|
||||
|
||||
// Adjust source owner count (legacy position, pre-Sponsor)
|
||||
if (!sponsorEnabled)
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, slep, reserveToSubtract, ctx_.journal);
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
|
||||
@@ -51,8 +51,8 @@ setCommonHostFunctions(HostFunctions& hfs, ImportVec& i)
|
||||
WASM_IMPORT_FUNC2(i, getCurrentLedgerObjNestedArrayLen, "home_le_inner_arr_len", hfs, 70);
|
||||
WASM_IMPORT_FUNC2(i, getLedgerObjNestedArrayLen, "le_inner_arr_len", hfs, 70);
|
||||
|
||||
WASM_IMPORT_FUNC2(i, checkSignature, "check_sig", hfs, 300);
|
||||
WASM_IMPORT_FUNC2(i, computeSha512HalfHash, "sha512_half", hfs, 2000);
|
||||
WASM_IMPORT_FUNC2(i, checkSignature, "check_sig", hfs, 35'000);
|
||||
WASM_IMPORT_FUNC2(i, computeSha512HalfHash, "sha512_half", hfs, 2'000);
|
||||
|
||||
WASM_IMPORT_FUNC2(i, accountKeylet, "accountroot_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, ammKeylet, "amm_id", hfs, 450);
|
||||
|
||||
@@ -903,7 +903,7 @@ WasmiEngine::check(
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
return temBAD_WASM;
|
||||
return temINVALID_BYTECODE;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
|
||||
@@ -6747,7 +6747,7 @@ private:
|
||||
using namespace test::jtx;
|
||||
|
||||
auto const testCase = [&](std::string suffix, FeatureBitset features) {
|
||||
testcase("Fail pseudo-account allocation " + suffix);
|
||||
testcase("Pseudo-account allocation failure " + suffix);
|
||||
std::string logs;
|
||||
Env env{*this, features, std::make_unique<CaptureLogs>(&logs)};
|
||||
env.fund(XRP(30'000), gw_, alice_);
|
||||
|
||||
1333
src/test/app/EscrowSmart_test.cpp
Normal file
1333
src/test/app/EscrowSmart_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1482,7 +1482,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";
|
||||
@@ -1639,6 +1639,8 @@ public:
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
testWithFeats(all);
|
||||
testWithFeats(all - featureTokenEscrow);
|
||||
testWithFeats(all - featureSmartEscrow);
|
||||
testWithFeats(all - featureTokenEscrow - featureSmartEscrow);
|
||||
testTags(all - fixIncludeKeyletFields);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -135,7 +135,7 @@ getSection(Bytes const& module, std::uint8_t n)
|
||||
}
|
||||
|
||||
static std::optional<int32_t>
|
||||
runFinishFunction(std::string const& code)
|
||||
runFinish(std::string const& code)
|
||||
{
|
||||
auto& engine = WasmEngine::instance();
|
||||
auto const wasm = hexToBytes(code);
|
||||
@@ -152,7 +152,7 @@ runFinishFunction(std::string const& code)
|
||||
static bool
|
||||
finishFunctionReturns(std::string const& code, int32_t expected)
|
||||
{
|
||||
auto const result = runFinishFunction(code);
|
||||
auto const result = runFinish(code);
|
||||
return result.has_value() && *result == expected;
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ struct Wasm_test : public beast::unit_test::Suite
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction wrong function name
|
||||
// Bytecode wrong function name
|
||||
// pub fn bad() -> bool {
|
||||
// unsafe { host_lib::getLedgerSqn() >= 5 }
|
||||
// }
|
||||
@@ -672,7 +672,7 @@ struct Wasm_test : public beast::unit_test::Suite
|
||||
auto const codecovWasm = hexToBytes(kCodecovTestsWasmHex);
|
||||
TestHostFunctions hfs(env);
|
||||
|
||||
auto const allowance = 125'667;
|
||||
auto const allowance = 264'467;
|
||||
auto re = runEscrowWasm(codecovWasm, hfs, allowance, escrowFunctionName, {});
|
||||
|
||||
checkResult(re, 1, allowance);
|
||||
@@ -715,16 +715,16 @@ struct Wasm_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase("Wasm additional memory limit tests");
|
||||
BEAST_EXPECT(finishFunctionReturns(kMemoryPointerAtLimitHex, 1));
|
||||
BEAST_EXPECT(!runFinishFunction(kMemoryPointerOverLimitHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kMemoryOffsetOverLimitHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kMemoryEndOfWordOverLimitHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kMemoryPointerOverLimitHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kMemoryOffsetOverLimitHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kMemoryEndOfWordOverLimitHex).has_value());
|
||||
BEAST_EXPECT(finishFunctionReturns(kMemoryGrow0To1PageHex, 1));
|
||||
BEAST_EXPECT(finishFunctionReturns(kMemoryGrow1To0PageHex, -1));
|
||||
BEAST_EXPECT(finishFunctionReturns(kMemoryLastByteOf8MbHex, 1));
|
||||
BEAST_EXPECT(finishFunctionReturns(kMemoryGrow1MoreThan8MbHex, -1));
|
||||
BEAST_EXPECT(finishFunctionReturns(kMemoryGrow0MoreThan8MbHex, 1));
|
||||
BEAST_EXPECT(!runFinishFunction(kMemoryInit1MoreThan8MbHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kMemoryNegativeAddressHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kMemoryInit1MoreThan8MbHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kMemoryNegativeAddressHex).has_value());
|
||||
}
|
||||
|
||||
void
|
||||
@@ -732,64 +732,64 @@ struct Wasm_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase("Wasm table limit tests");
|
||||
BEAST_EXPECT(finishFunctionReturns(kTable64ElementsHex, 1));
|
||||
BEAST_EXPECT(!runFinishFunction(kTable65ElementsHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kTable2TablesHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kTable65ElementsHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kTable2TablesHex).has_value());
|
||||
BEAST_EXPECT(finishFunctionReturns(kTable0ElementsHex, 1));
|
||||
BEAST_EXPECT(!runFinishFunction(kTableUintMaxHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kTableUintMaxHex).has_value());
|
||||
}
|
||||
|
||||
void
|
||||
testWasmProposal()
|
||||
{
|
||||
testcase("Wasm disabled proposal tests");
|
||||
BEAST_EXPECT(!runFinishFunction(kProposalMutableGlobalHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kProposalGcStructNewHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kProposalMultiValueHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kProposalSignExtHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kProposalFloatToIntHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kProposalBulkMemoryHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kProposalRefTypesHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kProposalTailCallHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kProposalExtendedConstHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kProposalMultiMemoryHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kProposalCustomPageSizesHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kProposalMemory64Hex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kProposalWideArithmeticHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kProposalMutableGlobalHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kProposalGcStructNewHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kProposalMultiValueHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kProposalSignExtHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kProposalFloatToIntHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kProposalBulkMemoryHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kProposalRefTypesHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kProposalTailCallHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kProposalExtendedConstHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kProposalMultiMemoryHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kProposalCustomPageSizesHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kProposalMemory64Hex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kProposalWideArithmeticHex).has_value());
|
||||
}
|
||||
|
||||
void
|
||||
testWasmTrap()
|
||||
{
|
||||
testcase("Wasm trap tests");
|
||||
BEAST_EXPECT(!runFinishFunction(kTrapDivideBy0Hex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kTrapIntOverflowHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kTrapUnreachableHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kTrapNullCallHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kTrapFuncSigMismatchHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kTrapDivideBy0Hex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kTrapIntOverflowHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kTrapUnreachableHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kTrapNullCallHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kTrapFuncSigMismatchHex).has_value());
|
||||
}
|
||||
|
||||
void
|
||||
testWasmWasi()
|
||||
{
|
||||
testcase("Wasm Wasi tests");
|
||||
BEAST_EXPECT(!runFinishFunction(kWasiGetTimeHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kWasiPrintHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kWasiGetTimeHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kWasiPrintHex).has_value());
|
||||
}
|
||||
|
||||
void
|
||||
testWasmSectionCorruption()
|
||||
{
|
||||
testcase("Wasm Section Corruption tests");
|
||||
BEAST_EXPECT(!runFinishFunction(kBadMagicNumberHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kBadVersionNumberHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kLyingHeaderHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kNeverEndingNumberHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kVectorLieHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kSectionOrderingHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kGhostPayloadHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kJunkAfterSectionHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kInvalidSectionIdHex).has_value());
|
||||
BEAST_EXPECT(!runFinishFunction(kLocalVariableBombHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kBadMagicNumberHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kBadVersionNumberHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kLyingHeaderHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kNeverEndingNumberHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kVectorLieHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kSectionOrderingHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kGhostPayloadHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kJunkAfterSectionHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kInvalidSectionIdHex).has_value());
|
||||
BEAST_EXPECT(!runFinish(kLocalVariableBombHex).has_value());
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -2,7 +2,108 @@
|
||||
|
||||
#include <test/app/wasm_fixtures/fixtures.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wasm_constants {
|
||||
|
||||
namespace {
|
||||
|
||||
void
|
||||
appendU32Leb(std::vector<uint8_t>& out, uint32_t value)
|
||||
{
|
||||
do
|
||||
{
|
||||
auto byte = static_cast<uint8_t>(value & 0x7f);
|
||||
value >>= 7;
|
||||
if (value != 0u)
|
||||
byte |= 0x80;
|
||||
out.push_back(byte);
|
||||
} while (value != 0u);
|
||||
}
|
||||
|
||||
void
|
||||
appendSection(std::vector<uint8_t>& out, uint8_t section, std::vector<uint8_t> const& payload)
|
||||
{
|
||||
out.push_back(section);
|
||||
appendU32Leb(out, payload.size());
|
||||
out.insert(out.end(), payload.begin(), payload.end());
|
||||
}
|
||||
|
||||
void
|
||||
appendBytes(std::vector<uint8_t>& out, auto const& bytes)
|
||||
{
|
||||
for (auto byte : bytes)
|
||||
out.push_back(byte);
|
||||
}
|
||||
|
||||
std::vector<uint8_t>
|
||||
baseModule()
|
||||
{
|
||||
std::vector<uint8_t> out;
|
||||
appendBytes(out, kWasmHeader);
|
||||
appendBytes(out, kTypeEmptyFunc);
|
||||
appendBytes(out, kFuncTypE0);
|
||||
appendBytes(out, kExportFinish);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<uint8_t>
|
||||
generateCodeBlob(uint32_t numInstructions)
|
||||
{
|
||||
auto out = baseModule();
|
||||
|
||||
std::vector<uint8_t> body;
|
||||
body.push_back(0x00);
|
||||
body.insert(body.end(), numInstructions, kInstrNop);
|
||||
body.push_back(kInstrEnd);
|
||||
|
||||
std::vector<uint8_t> codePayload;
|
||||
codePayload.push_back(0x01);
|
||||
appendU32Leb(codePayload, body.size());
|
||||
codePayload.insert(codePayload.end(), body.begin(), body.end());
|
||||
|
||||
appendSection(out, kSectionCode, codePayload);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<uint8_t>
|
||||
generateDataBlob(uint32_t dataSize)
|
||||
{
|
||||
std::vector<uint8_t> out;
|
||||
appendBytes(out, kWasmHeader);
|
||||
appendBytes(out, kTypeEmptyFunc);
|
||||
appendBytes(out, kFuncTypE0);
|
||||
|
||||
std::vector<uint8_t> memoryPayload;
|
||||
memoryPayload.push_back(0x01);
|
||||
memoryPayload.push_back(0x00);
|
||||
appendU32Leb(memoryPayload, (dataSize + 65'535) / 65'536);
|
||||
appendSection(out, kSectionMemory, memoryPayload);
|
||||
|
||||
appendBytes(out, kExportFinish);
|
||||
|
||||
std::vector<uint8_t> codePayload;
|
||||
codePayload.push_back(0x01);
|
||||
appendU32Leb(codePayload, sizeof(kEmptyBody));
|
||||
appendBytes(codePayload, kEmptyBody);
|
||||
appendSection(out, kSectionCode, codePayload);
|
||||
|
||||
std::vector<uint8_t> dataPayload;
|
||||
dataPayload.push_back(0x01);
|
||||
dataPayload.push_back(0x00);
|
||||
appendBytes(dataPayload, kDataOffsetZero);
|
||||
appendU32Leb(dataPayload, dataSize);
|
||||
dataPayload.insert(dataPayload.end(), dataSize, kDataFillByte);
|
||||
appendSection(out, kSectionData, dataPayload);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace wasm_constants
|
||||
|
||||
extern std::string const kFibWasmHex =
|
||||
"0061736d0100000001090260000060017f017f030302000105030100020638097f004180080b7f004180080b7f0041"
|
||||
@@ -1392,3 +1493,18 @@ extern std::string const kImpExpHex =
|
||||
"6e000003656e76166765745f706172656e745f6c65646765725f686173680000030403010201050301000107310406"
|
||||
"6d656d6f72790200096578705f66756e63310002096578705f66756e633200030c746573745f696d706f7274730004"
|
||||
"0a2b03040041010b0700200041026c0b1c01027f4120410410001a41202802002100410041201001210120000b";
|
||||
|
||||
extern std::string const kUpdateDataWasmHex =
|
||||
"0061736d01000000010e0360027f7f017f6000006000017f02100103656e76087365745f6461746100000303020102"
|
||||
"0503010002063f0a7f01419088040b7f004180080b7f004185080b7f004190080b7f00419088040b7f004180080b7f"
|
||||
"00419088040b7f00418080080b7f0041000b7f0041010b07b1010c066d656d6f72790200115f5f7761736d5f63616c"
|
||||
"6c5f63746f727300010d657363726f775f66696e69736800020c5f5f64736f5f68616e646c6503010a5f5f64617461"
|
||||
"5f656e6403020b5f5f737461636b5f6c6f7703030c5f5f737461636b5f6869676803040d5f5f676c6f62616c5f6261"
|
||||
"736503050b5f5f686561705f6261736503060a5f5f686561705f656e6403070d5f5f6d656d6f72795f626173650308"
|
||||
"0c5f5f7461626c655f6261736503090a3f0202000b3a01017f230041106b220024002000410c6a4184082d00003a00"
|
||||
"002000418008280000360208200041086a410410001a200041106a240041807e0b0b0b01004180080b044461746100"
|
||||
"7f0970726f647563657273010c70726f6365737365642d62790105636c616e675f31392e312e352d776173692d7364"
|
||||
"6b202868747470733a2f2f6769746875622e636f6d2f6c6c766d2f6c6c766d2d70726f6a6563742061623462356132"
|
||||
"6462353832393538616631656533303861373930636664623432626432343732302900490f7461726765745f666561"
|
||||
"7475726573042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f7265666572656e63652d7479"
|
||||
"7065732b0a6d756c746976616c7565";
|
||||
|
||||
@@ -2,7 +2,80 @@
|
||||
|
||||
// TODO: consider moving these to separate files (and figure out the build)
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// WASM binary format constants and helpers for building test modules
|
||||
namespace wasm_constants {
|
||||
|
||||
// Magic + version header
|
||||
uint8_t const kWasmHeader[] = {
|
||||
0x00,
|
||||
0x61,
|
||||
0x73,
|
||||
0x6d, // magic: \0asm
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00 // version: 1
|
||||
};
|
||||
|
||||
// Type section: () -> ()
|
||||
uint8_t const kTypeEmptyFunc[] = {0x01, 0x04, 0x01, 0x60, 0x00, 0x00};
|
||||
|
||||
// Function section: one function using type 0
|
||||
uint8_t const kFuncTypE0[] = {0x03, 0x02, 0x01, 0x00};
|
||||
|
||||
// Export section: export func 0 as "escrow_finish"
|
||||
uint8_t const kExportFinish[] = {
|
||||
0x07,
|
||||
0x11,
|
||||
0x01,
|
||||
0x0d,
|
||||
'e',
|
||||
's',
|
||||
'c',
|
||||
'r',
|
||||
'o',
|
||||
'w',
|
||||
'_',
|
||||
'f',
|
||||
'i',
|
||||
'n',
|
||||
'i',
|
||||
's',
|
||||
'h',
|
||||
0x00,
|
||||
0x00};
|
||||
|
||||
// Empty function body: 0 locals, end
|
||||
uint8_t const kEmptyBody[] = {0x00, 0x0b};
|
||||
|
||||
// Data segment offset: i32.const 0, end
|
||||
uint8_t const kDataOffsetZero[] = {0x41, 0x00, 0x0b};
|
||||
|
||||
// Section IDs
|
||||
uint8_t const kSectionMemory = 0x05;
|
||||
uint8_t const kSectionCode = 0x0a;
|
||||
uint8_t const kSectionData = 0x0b;
|
||||
|
||||
// Instructions
|
||||
uint8_t const kInstrNop = 0x01;
|
||||
uint8_t const kInstrEnd = 0x0b;
|
||||
|
||||
// Fill byte for data section bloat
|
||||
uint8_t const kDataFillByte = 0xEE;
|
||||
|
||||
// Generator for WASM module with large code section (many NOPs)
|
||||
std::vector<uint8_t>
|
||||
generateCodeBlob(uint32_t numInstructions);
|
||||
|
||||
// Generator for WASM module with large data section
|
||||
std::vector<uint8_t>
|
||||
generateDataBlob(uint32_t dataSize);
|
||||
|
||||
} // namespace wasm_constants
|
||||
|
||||
extern std::string const kLedgerSqnWasmHex;
|
||||
extern std::string const kAllHostFunctionsWasmHex;
|
||||
@@ -81,3 +154,4 @@ extern std::string const kFunctions5kHex;
|
||||
extern std::string const kOpcReservedHex;
|
||||
|
||||
extern std::string const kImpExpHex;
|
||||
extern std::string const kUpdateDataWasmHex;
|
||||
|
||||
11
src/test/app/wasm_fixtures/updateData.c
Normal file
11
src/test/app/wasm_fixtures/updateData.c
Normal file
@@ -0,0 +1,11 @@
|
||||
#include <stdint.h>
|
||||
|
||||
int32_t set_data(uint8_t const *, int32_t);
|
||||
|
||||
int escrow_finish()
|
||||
{
|
||||
uint8_t buf[] = "Data";
|
||||
set_data(buf, sizeof(buf) - 1);
|
||||
|
||||
return -256;
|
||||
}
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/JTx.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
@@ -11,7 +14,10 @@
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* Escrow operations.
|
||||
@@ -86,4 +92,73 @@ auto const kCondition = JTxFieldWrapper<BlobField>(sfCondition);
|
||||
|
||||
auto const kFulfillment = JTxFieldWrapper<BlobField>(sfFulfillment);
|
||||
|
||||
struct Bytecode
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit Bytecode(std::string func) : value_(std::move(func))
|
||||
{
|
||||
}
|
||||
|
||||
explicit Bytecode(Slice const& func) : value_(strHex(func))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit Bytecode(std::array<std::uint8_t, N> const& f) : Bytecode(makeSlice(f))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfBytecode.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct Data
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit Data(std::string func) : value_(std::move(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 Gas
|
||||
{
|
||||
private:
|
||||
std::uint32_t value_;
|
||||
|
||||
public:
|
||||
explicit Gas(std::uint32_t const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfGas.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::test::jtx::escrow
|
||||
|
||||
@@ -21,12 +21,14 @@ 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.referenceFee = UNIT_TEST_REFERENCE_FEE;
|
||||
cfg.fees.accountReserve = XRP(200).value().xrp().drops();
|
||||
cfg.fees.ownerReserve = XRP(50).value().xrp().drops();
|
||||
cfg.fees.gasLimit = 1'000'000;
|
||||
cfg.fees.bytecodeSizeLimit = 1'000'000;
|
||||
cfg.fees.gasPrice = 1'000;
|
||||
cfg.fees.bytecodeSizeLimit = 100'000;
|
||||
cfg.fees.gasPrice = 1'000'000; // 1 drop = 1,000,000 micro-drops
|
||||
|
||||
// The Beta API (currently v2) is always available to tests
|
||||
cfg.betaRpcApi = true;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/core/NetworkIDService.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/PendingSaves.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/server/LoadFeeTrack.h>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
@@ -71,11 +72,22 @@ private:
|
||||
*/
|
||||
class TestServiceRegistry : public ServiceRegistry
|
||||
{
|
||||
static Fees
|
||||
defaultFees()
|
||||
{
|
||||
Fees fees{XRPAmount{10}, XRPAmount{10 * kDropsPerXrp}, XRPAmount{2 * kDropsPerXrp}};
|
||||
fees.gasLimit = 1'000'000;
|
||||
fees.bytecodeSizeLimit = 100'000;
|
||||
fees.gasPrice = 1'000'000;
|
||||
return fees;
|
||||
}
|
||||
|
||||
TestLogs logs_{beast::Severity::Warning};
|
||||
boost::asio::io_context ioContext_;
|
||||
TestFamily family_{logs_.journal("TestFamily")};
|
||||
LoadFeeTrack feeTrack_{logs_.journal("LoadFeeTrack")};
|
||||
TestNetworkIDService networkIDService_;
|
||||
Fees fees_{defaultFees()};
|
||||
HashRouter hashRouter_{HashRouter::Setup{}, stopwatch()};
|
||||
NodeCache tempNodeCache_{
|
||||
"TempNodeCache",
|
||||
@@ -374,6 +386,12 @@ public:
|
||||
throw std::logic_error("TestServiceRegistry::getWalletDB() not implemented");
|
||||
}
|
||||
|
||||
Fees
|
||||
getFees() const override
|
||||
{
|
||||
return fees_;
|
||||
}
|
||||
|
||||
// Temporary: Get the underlying Application
|
||||
Application&
|
||||
getApp() override
|
||||
|
||||
@@ -27,6 +27,8 @@ TEST(EscrowTests, BuilderSettersRoundTrip)
|
||||
auto const conditionValue = canonical_VL();
|
||||
auto const cancelAfterValue = canonical_UINT32();
|
||||
auto const finishAfterValue = canonical_UINT32();
|
||||
auto const bytecodeValue = canonical_VL();
|
||||
auto const dataValue = canonical_VL();
|
||||
auto const sourceTagValue = canonical_UINT32();
|
||||
auto const destinationTagValue = canonical_UINT32();
|
||||
auto const ownerNodeValue = canonical_UINT64();
|
||||
@@ -49,6 +51,8 @@ TEST(EscrowTests, BuilderSettersRoundTrip)
|
||||
builder.setCondition(conditionValue);
|
||||
builder.setCancelAfter(cancelAfterValue);
|
||||
builder.setFinishAfter(finishAfterValue);
|
||||
builder.setBytecode(bytecodeValue);
|
||||
builder.setData(dataValue);
|
||||
builder.setSourceTag(sourceTagValue);
|
||||
builder.setDestinationTag(destinationTagValue);
|
||||
builder.setDestinationNode(destinationNodeValue);
|
||||
@@ -132,6 +136,22 @@ TEST(EscrowTests, BuilderSettersRoundTrip)
|
||||
EXPECT_TRUE(entry.hasFinishAfter());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = bytecodeValue;
|
||||
auto const actualOpt = entry.getBytecode();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfBytecode");
|
||||
EXPECT_TRUE(entry.hasBytecode());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = dataValue;
|
||||
auto const actualOpt = entry.getData();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfData");
|
||||
EXPECT_TRUE(entry.hasData());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sourceTagValue;
|
||||
auto const actualOpt = entry.getSourceTag();
|
||||
@@ -192,6 +212,8 @@ TEST(EscrowTests, BuilderFromSleRoundTrip)
|
||||
auto const conditionValue = canonical_VL();
|
||||
auto const cancelAfterValue = canonical_UINT32();
|
||||
auto const finishAfterValue = canonical_UINT32();
|
||||
auto const bytecodeValue = canonical_VL();
|
||||
auto const dataValue = canonical_VL();
|
||||
auto const sourceTagValue = canonical_UINT32();
|
||||
auto const destinationTagValue = canonical_UINT32();
|
||||
auto const ownerNodeValue = canonical_UINT64();
|
||||
@@ -210,6 +232,8 @@ TEST(EscrowTests, BuilderFromSleRoundTrip)
|
||||
sle->at(sfCondition) = conditionValue;
|
||||
sle->at(sfCancelAfter) = cancelAfterValue;
|
||||
sle->at(sfFinishAfter) = finishAfterValue;
|
||||
sle->at(sfBytecode) = bytecodeValue;
|
||||
sle->at(sfData) = dataValue;
|
||||
sle->at(sfSourceTag) = sourceTagValue;
|
||||
sle->at(sfDestinationTag) = destinationTagValue;
|
||||
sle->at(sfOwnerNode) = ownerNodeValue;
|
||||
@@ -340,6 +364,32 @@ TEST(EscrowTests, BuilderFromSleRoundTrip)
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfFinishAfter");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = bytecodeValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getBytecode();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getBytecode();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfBytecode");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfBytecode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = dataValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getData();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getData();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfData");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfData");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sourceTagValue;
|
||||
|
||||
@@ -477,6 +527,10 @@ TEST(EscrowTests, OptionalFieldsReturnNullopt)
|
||||
EXPECT_FALSE(entry.getCancelAfter().has_value());
|
||||
EXPECT_FALSE(entry.hasFinishAfter());
|
||||
EXPECT_FALSE(entry.getFinishAfter().has_value());
|
||||
EXPECT_FALSE(entry.hasBytecode());
|
||||
EXPECT_FALSE(entry.getBytecode().has_value());
|
||||
EXPECT_FALSE(entry.hasData());
|
||||
EXPECT_FALSE(entry.getData().has_value());
|
||||
EXPECT_FALSE(entry.hasSourceTag());
|
||||
EXPECT_FALSE(entry.getSourceTag().has_value());
|
||||
EXPECT_FALSE(entry.hasDestinationTag());
|
||||
|
||||
@@ -30,11 +30,13 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip)
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const destinationValue = canonical_ACCOUNT();
|
||||
auto const destinationTagValue = canonical_UINT32();
|
||||
auto const amountValue = canonical_AMOUNT();
|
||||
auto const conditionValue = canonical_VL();
|
||||
auto const cancelAfterValue = canonical_UINT32();
|
||||
auto const finishAfterValue = canonical_UINT32();
|
||||
auto const destinationTagValue = canonical_UINT32();
|
||||
auto const bytecodeValue = canonical_VL();
|
||||
auto const dataValue = canonical_VL();
|
||||
|
||||
EscrowCreateBuilder builder{
|
||||
accountValue,
|
||||
@@ -45,10 +47,12 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip)
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
builder.setDestinationTag(destinationTagValue);
|
||||
builder.setCondition(conditionValue);
|
||||
builder.setCancelAfter(cancelAfterValue);
|
||||
builder.setFinishAfter(finishAfterValue);
|
||||
builder.setDestinationTag(destinationTagValue);
|
||||
builder.setBytecode(bytecodeValue);
|
||||
builder.setData(dataValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
@@ -78,6 +82,14 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip)
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = destinationTagValue;
|
||||
auto const actualOpt = tx.getDestinationTag();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfDestinationTag");
|
||||
EXPECT_TRUE(tx.hasDestinationTag());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = conditionValue;
|
||||
auto const actualOpt = tx.getCondition();
|
||||
@@ -103,11 +115,19 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip)
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = destinationTagValue;
|
||||
auto const actualOpt = tx.getDestinationTag();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfDestinationTag");
|
||||
EXPECT_TRUE(tx.hasDestinationTag());
|
||||
auto const& expected = bytecodeValue;
|
||||
auto const actualOpt = tx.getBytecode();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfBytecode should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfBytecode");
|
||||
EXPECT_TRUE(tx.hasBytecode());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = dataValue;
|
||||
auto const actualOpt = tx.getData();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfData should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfData");
|
||||
EXPECT_TRUE(tx.hasData());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -127,11 +147,13 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip)
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const destinationValue = canonical_ACCOUNT();
|
||||
auto const destinationTagValue = canonical_UINT32();
|
||||
auto const amountValue = canonical_AMOUNT();
|
||||
auto const conditionValue = canonical_VL();
|
||||
auto const cancelAfterValue = canonical_UINT32();
|
||||
auto const finishAfterValue = canonical_UINT32();
|
||||
auto const destinationTagValue = canonical_UINT32();
|
||||
auto const bytecodeValue = canonical_VL();
|
||||
auto const dataValue = canonical_VL();
|
||||
|
||||
// Build an initial transaction
|
||||
EscrowCreateBuilder initialBuilder{
|
||||
@@ -142,10 +164,12 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip)
|
||||
feeValue
|
||||
};
|
||||
|
||||
initialBuilder.setDestinationTag(destinationTagValue);
|
||||
initialBuilder.setCondition(conditionValue);
|
||||
initialBuilder.setCancelAfter(cancelAfterValue);
|
||||
initialBuilder.setFinishAfter(finishAfterValue);
|
||||
initialBuilder.setDestinationTag(destinationTagValue);
|
||||
initialBuilder.setBytecode(bytecodeValue);
|
||||
initialBuilder.setData(dataValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
@@ -176,6 +200,13 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip)
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = destinationTagValue;
|
||||
auto const actualOpt = rebuiltTx.getDestinationTag();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfDestinationTag");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = conditionValue;
|
||||
auto const actualOpt = rebuiltTx.getCondition();
|
||||
@@ -198,10 +229,17 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip)
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = destinationTagValue;
|
||||
auto const actualOpt = rebuiltTx.getDestinationTag();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfDestinationTag");
|
||||
auto const& expected = bytecodeValue;
|
||||
auto const actualOpt = rebuiltTx.getBytecode();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfBytecode should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfBytecode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = dataValue;
|
||||
auto const actualOpt = rebuiltTx.getData();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfData should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfData");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -263,14 +301,18 @@ TEST(TransactionsEscrowCreateTests, OptionalFieldsReturnNullopt)
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(tx.hasDestinationTag());
|
||||
EXPECT_FALSE(tx.getDestinationTag().has_value());
|
||||
EXPECT_FALSE(tx.hasCondition());
|
||||
EXPECT_FALSE(tx.getCondition().has_value());
|
||||
EXPECT_FALSE(tx.hasCancelAfter());
|
||||
EXPECT_FALSE(tx.getCancelAfter().has_value());
|
||||
EXPECT_FALSE(tx.hasFinishAfter());
|
||||
EXPECT_FALSE(tx.getFinishAfter().has_value());
|
||||
EXPECT_FALSE(tx.hasDestinationTag());
|
||||
EXPECT_FALSE(tx.getDestinationTag().has_value());
|
||||
EXPECT_FALSE(tx.hasBytecode());
|
||||
EXPECT_FALSE(tx.getBytecode().has_value());
|
||||
EXPECT_FALSE(tx.hasData());
|
||||
EXPECT_FALSE(tx.getData().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ TEST(TransactionsEscrowFinishTests, BuilderSettersRoundTrip)
|
||||
auto const fulfillmentValue = canonical_VL();
|
||||
auto const conditionValue = canonical_VL();
|
||||
auto const credentialIDsValue = canonical_VECTOR256();
|
||||
auto const gasValue = canonical_UINT32();
|
||||
|
||||
EscrowFinishBuilder builder{
|
||||
accountValue,
|
||||
@@ -47,6 +48,7 @@ TEST(TransactionsEscrowFinishTests, BuilderSettersRoundTrip)
|
||||
builder.setFulfillment(fulfillmentValue);
|
||||
builder.setCondition(conditionValue);
|
||||
builder.setCredentialIDs(credentialIDsValue);
|
||||
builder.setGas(gasValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
@@ -100,6 +102,14 @@ TEST(TransactionsEscrowFinishTests, BuilderSettersRoundTrip)
|
||||
EXPECT_TRUE(tx.hasCredentialIDs());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gasValue;
|
||||
auto const actualOpt = tx.getGas();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGas should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfGas");
|
||||
EXPECT_TRUE(tx.hasGas());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
@@ -121,6 +131,7 @@ TEST(TransactionsEscrowFinishTests, BuilderFromStTxRoundTrip)
|
||||
auto const fulfillmentValue = canonical_VL();
|
||||
auto const conditionValue = canonical_VL();
|
||||
auto const credentialIDsValue = canonical_VECTOR256();
|
||||
auto const gasValue = canonical_UINT32();
|
||||
|
||||
// Build an initial transaction
|
||||
EscrowFinishBuilder initialBuilder{
|
||||
@@ -134,6 +145,7 @@ TEST(TransactionsEscrowFinishTests, BuilderFromStTxRoundTrip)
|
||||
initialBuilder.setFulfillment(fulfillmentValue);
|
||||
initialBuilder.setCondition(conditionValue);
|
||||
initialBuilder.setCredentialIDs(credentialIDsValue);
|
||||
initialBuilder.setGas(gasValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
@@ -185,6 +197,13 @@ TEST(TransactionsEscrowFinishTests, BuilderFromStTxRoundTrip)
|
||||
expectEqualField(expected, *actualOpt, "sfCredentialIDs");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gasValue;
|
||||
auto const actualOpt = rebuiltTx.getGas();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGas should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfGas");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
@@ -250,6 +269,8 @@ TEST(TransactionsEscrowFinishTests, OptionalFieldsReturnNullopt)
|
||||
EXPECT_FALSE(tx.getCondition().has_value());
|
||||
EXPECT_FALSE(tx.hasCredentialIDs());
|
||||
EXPECT_FALSE(tx.getCredentialIDs().has_value());
|
||||
EXPECT_FALSE(tx.hasGas());
|
||||
EXPECT_FALSE(tx.getGas().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -826,6 +826,24 @@ public:
|
||||
return *walletDB_;
|
||||
}
|
||||
|
||||
Fees
|
||||
getFees() const override
|
||||
{
|
||||
XRPL_ASSERT(config_, "xrpl::ApplicationImp::getFees : non-null config");
|
||||
|
||||
auto const& f1(config_->fees);
|
||||
|
||||
Fees f2;
|
||||
f2.base = f1.referenceFee;
|
||||
f2.reserve = f1.accountReserve;
|
||||
f2.increment = f1.ownerReserve;
|
||||
f2.gasLimit = f1.gasLimit;
|
||||
f2.bytecodeSizeLimit = f1.bytecodeSizeLimit;
|
||||
f2.gasPrice = f1.gasPrice;
|
||||
|
||||
return f2;
|
||||
}
|
||||
|
||||
bool
|
||||
serverOkay(std::string& reason) override;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user