move changes over

This commit is contained in:
Mayukha Vadari
2025-09-18 15:59:05 -04:00
parent edfed06001
commit 817f9c4f8c
25 changed files with 1583 additions and 1155 deletions

View File

@@ -20,8 +20,9 @@
#ifndef RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED #ifndef RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
#define RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED #define RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
#include <xrpl/ledger/OpenView.h> #include <xrpld/ledger/OpenView.h>
#include <xrpl/ledger/detail/ApplyViewBase.h> #include <xrpld/ledger/detail/ApplyViewBase.h>
#include <xrpl/protocol/STAmount.h> #include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h> #include <xrpl/protocol/TER.h>
@@ -74,6 +75,18 @@ public:
deliver_ = amount; deliver_ = amount;
} }
void
setGasUsed(std::optional<std::uint32_t> const gasUsed)
{
gasUsed_ = gasUsed;
}
void
setWasmReturnCode(std::int32_t const wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
/** Get the number of modified entries /** Get the number of modified entries
*/ */
std::size_t std::size_t
@@ -92,6 +105,8 @@ public:
private: private:
std::optional<STAmount> deliver_; std::optional<STAmount> deliver_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> wasmReturnCode_;
}; };
} // namespace ripple } // namespace ripple

View File

@@ -17,147 +17,98 @@
*/ */
//============================================================================== //==============================================================================
#ifndef RIPPLE_LEDGER_APPLYSTATETABLE_H_INCLUDED #ifndef RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
#define RIPPLE_LEDGER_APPLYSTATETABLE_H_INCLUDED #define RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
#include <xrpl/beast/utility/Journal.h> #include <xrpld/ledger/OpenView.h>
#include <xrpl/ledger/OpenView.h> #include <xrpld/ledger/detail/ApplyViewBase.h>
#include <xrpl/ledger/RawView.h>
#include <xrpl/ledger/ReadView.h> #include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h> #include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxMeta.h>
#include <xrpl/protocol/XRPAmount.h>
#include <memory>
namespace ripple { namespace ripple {
namespace detail {
// Helper class that buffers modifications /** Editable, discardable view that can build metadata for one tx.
class ApplyStateTable
Iteration of the tx map is delegated to the base.
@note Presented as ApplyView to clients.
*/
class ApplyViewImpl final : public detail::ApplyViewBase
{ {
public: public:
using key_type = ReadView::key_type; ApplyViewImpl() = delete;
ApplyViewImpl(ApplyViewImpl const&) = delete;
ApplyViewImpl&
operator=(ApplyViewImpl&&) = delete;
ApplyViewImpl&
operator=(ApplyViewImpl const&) = delete;
private: ApplyViewImpl(ApplyViewImpl&&) = default;
enum class Action { ApplyViewImpl(ReadView const* base, ApplyFlags flags);
cache,
erase,
insert,
modify,
};
using items_t = std::map<key_type, std::pair<Action, std::shared_ptr<SLE>>>; /** Apply the transaction.
items_t items_;
XRPAmount dropsDestroyed_{0};
public:
ApplyStateTable() = default;
ApplyStateTable(ApplyStateTable&&) = default;
ApplyStateTable(ApplyStateTable const&) = delete;
ApplyStateTable&
operator=(ApplyStateTable&&) = delete;
ApplyStateTable&
operator=(ApplyStateTable const&) = delete;
void
apply(RawView& to) const;
After a call to `apply`, the only valid
operation on this object is to call the
destructor.
*/
std::optional<TxMeta> std::optional<TxMeta>
apply( apply(
OpenView& to, OpenView& to,
STTx const& tx, STTx const& tx,
TER ter, TER ter,
std::optional<STAmount> const& deliver, std::optional<uint256> parentBatchId,
std::optional<uint256 const> const& parentBatchId,
bool isDryRun, bool isDryRun,
beast::Journal j); beast::Journal j);
bool /** Set the amount of currency delivered.
exists(ReadView const& base, Keylet const& k) const;
std::optional<key_type> This value is used when generating metadata
succ( for payments, to set the DeliveredAmount field.
ReadView const& base, If the amount is not specified, the field is
key_type const& key, excluded from the resulting metadata.
std::optional<key_type> const& last) const; */
void
std::shared_ptr<SLE const> deliver(STAmount const& amount)
read(ReadView const& base, Keylet const& k) const; {
deliver_ = amount;
std::shared_ptr<SLE> }
peek(ReadView const& base, Keylet const& k);
std::size_t
size() const;
void
setGasUsed(std::optional<std::uint32_t> const gasUsed)
{
gasUsed_ = gasUsed;
}
void
setWasmReturnCode(std::int32_t const wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
/** Get the number of modified entries
*/
std::size_t
size();
/** Visit modified entries
*/
void void
visit( visit(
ReadView const& base, OpenView& target,
std::function<void( std::function<void(
uint256 const& key, uint256 const& key,
bool isDelete, bool isDelete,
std::shared_ptr<SLE const> const& before, std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)> const& func) const; std::shared_ptr<SLE const> const& after)> const& func);
void
erase(ReadView const& base, std::shared_ptr<SLE> const& sle);
void
rawErase(ReadView const& base, std::shared_ptr<SLE> const& sle);
void
insert(ReadView const& base, std::shared_ptr<SLE> const& sle);
void
update(ReadView const& base, std::shared_ptr<SLE> const& sle);
void
replace(ReadView const& base, std::shared_ptr<SLE> const& sle);
void
destroyXRP(XRPAmount const& fee);
// For debugging
XRPAmount const&
dropsDestroyed() const
{
return dropsDestroyed_;
}
private: private:
using Mods = hash_map<key_type, std::shared_ptr<SLE>>; std::optional<STAmount> deliver_;
std::optional<std::uint32_t> gasUsed_;
static void std::optional<std::int32_t> wasmReturnCode_;
threadItem(TxMeta& meta, std::shared_ptr<SLE> const& to);
std::shared_ptr<SLE>
getForMod(
ReadView const& base,
key_type const& key,
Mods& mods,
beast::Journal j);
void
threadTx(
ReadView const& base,
TxMeta& meta,
AccountID const& to,
Mods& mods,
beast::Journal j);
void
threadOwners(
ReadView const& base,
TxMeta& meta,
std::shared_ptr<SLE const> const& sle,
Mods& mods,
beast::Journal j);
}; };
} // namespace detail
} // namespace ripple } // namespace ripple
#endif #endif

View File

@@ -24,6 +24,8 @@
namespace ripple { namespace ripple {
constexpr std::uint32_t MICRO_DROPS_PER_DROP{1'000'000};
/** Reflects the fee settings for a particular ledger. /** Reflects the fee settings for a particular ledger.
The fees are always the same for any transactions applied The fees are always the same for any transactions applied
@@ -34,6 +36,10 @@ 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)
std::uint32_t gasPrice{0}; // price of WASM gas (micro-drops)
explicit Fees() = default; explicit Fees() = default;
Fees(Fees const&) = default; Fees(Fees const&) = default;

View File

@@ -231,6 +231,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;
@@ -287,11 +293,9 @@ delegate(AccountID const& account, AccountID const& authorizedAccount) noexcept;
Keylet Keylet
bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType); bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType);
// `seq` is stored as `sfXChainClaimID` in the object
Keylet Keylet
xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq); xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq);
// `seq` is stored as `sfXChainAccountCreateCount` in the object
Keylet Keylet
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq); xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq);

View File

@@ -22,6 +22,7 @@
#include <xrpl/basics/ByteUtilities.h> #include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/basics/partitioned_unordered_map.h>
#include <cstdint> #include <cstdint>

View File

@@ -187,6 +187,8 @@ enum TEFcodes : TERUnderlyingType {
tefNO_TICKET, tefNO_TICKET,
tefNFTOKEN_IS_NOT_TRANSFERABLE, tefNFTOKEN_IS_NOT_TRANSFERABLE,
tefINVALID_LEDGER_FIX_TYPE, tefINVALID_LEDGER_FIX_TYPE,
tefNO_WASM,
tefWASM_FIELD_NOT_INCLUDED,
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -364,6 +366,7 @@ enum TECcodes : TERUnderlyingType {
tecPSEUDO_ACCOUNT = 196, tecPSEUDO_ACCOUNT = 196,
tecPRECISION_LOSS = 197, tecPRECISION_LOSS = 197,
tecNO_DELEGATE_PERMISSION = 198, tecNO_DELEGATE_PERMISSION = 198,
tecWASM_REJECTED = 199,
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@@ -46,10 +46,7 @@ private:
CtorHelper); CtorHelper);
public: public:
TxMeta( TxMeta(uint256 const& transactionID, std::uint32_t ledger);
uint256 const& transactionID,
std::uint32_t ledger,
std::optional<uint256> parentBatchId = std::nullopt);
TxMeta(uint256 const& txID, std::uint32_t ledger, Blob const&); TxMeta(uint256 const& txID, std::uint32_t ledger, Blob const&);
TxMeta(uint256 const& txID, std::uint32_t ledger, std::string const&); TxMeta(uint256 const& txID, std::uint32_t ledger, std::string const&);
TxMeta(uint256 const& txID, std::uint32_t ledger, STObject const&); TxMeta(uint256 const& txID, std::uint32_t ledger, STObject const&);
@@ -136,7 +133,7 @@ public:
void void
setParentBatchId(uint256 const& parentBatchId) setParentBatchId(uint256 const& parentBatchId)
{ {
mParentBatchId = parentBatchId; parentBatchId_ = parentBatchId;
} }
uint256 uint256
@@ -145,13 +142,55 @@ public:
XRPL_ASSERT( XRPL_ASSERT(
hasParentBatchId(), hasParentBatchId(),
"ripple::TxMeta::getParentBatchId : non-null batch id"); "ripple::TxMeta::getParentBatchId : non-null batch id");
return *mParentBatchId; return *parentBatchId_;
} }
bool bool
hasParentBatchId() const hasParentBatchId() const
{ {
return static_cast<bool>(mParentBatchId); return static_cast<bool>(parentBatchId_);
}
void
setGasUsed(std::uint32_t const& gasUsed)
{
gasUsed_ = gasUsed;
}
std::uint32_t
getGasUsed() const
{
XRPL_ASSERT(
hasGasUsed(),
"ripple::TxMeta::getGasUsed : non-null gas used field");
return *gasUsed_;
}
bool
hasGasUsed() const
{
return static_cast<bool>(gasUsed_);
}
void
setWasmReturnCode(std::int32_t const& wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
std::int32_t
getWasmReturnCode() const
{
XRPL_ASSERT(
hasWasmReturnCode(),
"ripple::TxMeta::getWasmReturnCode : non-null wasm return code");
return *wasmReturnCode_;
}
bool
hasWasmReturnCode() const
{
return static_cast<bool>(wasmReturnCode_);
} }
private: private:
@@ -161,7 +200,9 @@ private:
int mResult; int mResult;
std::optional<STAmount> mDelivered; std::optional<STAmount> mDelivered;
std::optional<uint256> mParentBatchId; std::optional<uint256> parentBatchId_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> wasmReturnCode_;
STArray mNodes; STArray mNodes;
}; };

View File

@@ -32,9 +32,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.
XRPL_FIX (IncludeKeyletFields, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(SmartEscrow, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(DynamicMPT, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (TokenEscrowV1, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (DelegateV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (DelegateV1_1, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo)
@@ -47,7 +45,6 @@ XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo
XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegation, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionDelegation, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
// Check flags in Credential transactions
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

@@ -120,7 +120,6 @@ LEDGER_ENTRY(ltNFTOKEN_PAGE, 0x0050, NFTokenPage, nft_page, ({
// All fields are soeREQUIRED because there is always a SignerEntries. // All fields are soeREQUIRED because there is always a SignerEntries.
// If there are no SignerEntries the node is deleted. // If there are no SignerEntries the node is deleted.
LEDGER_ENTRY(ltSIGNER_LIST, 0x0053, SignerList, signer_list, ({ LEDGER_ENTRY(ltSIGNER_LIST, 0x0053, SignerList, signer_list, ({
{sfOwner, soeOPTIONAL},
{sfOwnerNode, soeREQUIRED}, {sfOwnerNode, soeREQUIRED},
{sfSignerQuorum, soeREQUIRED}, {sfSignerQuorum, soeREQUIRED},
{sfSignerEntries, soeREQUIRED}, {sfSignerEntries, soeREQUIRED},
@@ -189,7 +188,7 @@ LEDGER_ENTRY(ltDIR_NODE, 0x0064, DirectoryNode, directory, ({
{sfNFTokenID, soeOPTIONAL}, {sfNFTokenID, soeOPTIONAL},
{sfPreviousTxnID, soeOPTIONAL}, {sfPreviousTxnID, soeOPTIONAL},
{sfPreviousTxnLgrSeq, soeOPTIONAL}, {sfPreviousTxnLgrSeq, soeOPTIONAL},
{sfDomainID, soeOPTIONAL} // order book directories {sfDomainID, soeOPTIONAL}
})) }))
/** The ledger object which lists details about amendments on the network. /** The ledger object which lists details about amendments on the network.
@@ -320,6 +319,7 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
{sfBaseFeeDrops, soeOPTIONAL}, {sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL}, {sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL}, {sfReserveIncrementDrops, soeOPTIONAL},
{sfPreviousTxnID, soeOPTIONAL}, {sfPreviousTxnID, soeOPTIONAL},
{sfPreviousTxnLgrSeq, soeOPTIONAL}, {sfPreviousTxnLgrSeq, soeOPTIONAL},
})) }))
@@ -344,12 +344,13 @@ LEDGER_ENTRY(ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID, 0x0074, XChainOwnedCreateAc
*/ */
LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
{sfAccount, soeREQUIRED}, {sfAccount, soeREQUIRED},
{sfSequence, soeOPTIONAL},
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED}, {sfAmount, soeREQUIRED},
{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},
@@ -367,7 +368,6 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, payment_channel, ({ LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, payment_channel, ({
{sfAccount, soeREQUIRED}, {sfAccount, soeREQUIRED},
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
{sfSequence, soeOPTIONAL},
{sfAmount, soeREQUIRED}, {sfAmount, soeREQUIRED},
{sfBalance, soeREQUIRED}, {sfBalance, soeREQUIRED},
{sfPublicKey, soeREQUIRED}, {sfPublicKey, soeREQUIRED},
@@ -415,7 +415,6 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
{sfPreviousTxnID, soeREQUIRED}, {sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED}, {sfPreviousTxnLgrSeq, soeREQUIRED},
{sfDomainID, soeOPTIONAL}, {sfDomainID, soeOPTIONAL},
{sfMutableFlags, soeDEFAULT},
})) }))
/** A ledger object which tracks MPToken /** A ledger object which tracks MPToken
@@ -436,7 +435,6 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
*/ */
LEDGER_ENTRY(ltORACLE, 0x0080, Oracle, oracle, ({ LEDGER_ENTRY(ltORACLE, 0x0080, Oracle, oracle, ({
{sfOwner, soeREQUIRED}, {sfOwner, soeREQUIRED},
{sfOracleDocumentID, soeOPTIONAL},
{sfProvider, soeREQUIRED}, {sfProvider, soeREQUIRED},
{sfPriceDataSeries, soeREQUIRED}, {sfPriceDataSeries, soeREQUIRED},
{sfAssetClass, soeREQUIRED}, {sfAssetClass, soeREQUIRED},

View File

@@ -114,7 +114,11 @@ 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(sfPermissionValue, UINT32, 52) TYPED_SFIELD(sfPermissionValue, UINT32, 52)
TYPED_SFIELD(sfMutableFlags, UINT32, 53) TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 53)
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 54)
TYPED_SFIELD(sfGasPrice, UINT32, 55)
TYPED_SFIELD(sfComputationAllowance, UINT32, 56)
TYPED_SFIELD(sfGasUsed, UINT32, 57)
// 64-bit integers (common) // 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1) TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -174,8 +178,7 @@ TYPED_SFIELD(sfNFTokenID, UINT256, 10)
TYPED_SFIELD(sfEmitParentTxnID, UINT256, 11) TYPED_SFIELD(sfEmitParentTxnID, UINT256, 11)
TYPED_SFIELD(sfEmitNonce, UINT256, 12) TYPED_SFIELD(sfEmitNonce, UINT256, 12)
TYPED_SFIELD(sfEmitHookHash, UINT256, 13) TYPED_SFIELD(sfEmitHookHash, UINT256, 13)
TYPED_SFIELD(sfAMMID, UINT256, 14, TYPED_SFIELD(sfAMMID, UINT256, 14)
SField::sMD_PseudoAccount | SField::sMD_Default)
// 256-bit (uncommon) // 256-bit (uncommon)
TYPED_SFIELD(sfBookDirectory, UINT256, 16) TYPED_SFIELD(sfBookDirectory, UINT256, 16)
@@ -197,8 +200,7 @@ TYPED_SFIELD(sfHookHash, UINT256, 31)
TYPED_SFIELD(sfHookNamespace, UINT256, 32) TYPED_SFIELD(sfHookNamespace, UINT256, 32)
TYPED_SFIELD(sfHookSetTxnID, UINT256, 33) TYPED_SFIELD(sfHookSetTxnID, UINT256, 33)
TYPED_SFIELD(sfDomainID, UINT256, 34) TYPED_SFIELD(sfDomainID, UINT256, 34)
TYPED_SFIELD(sfVaultID, UINT256, 35, TYPED_SFIELD(sfVaultID, UINT256, 35)
SField::sMD_PseudoAccount | SField::sMD_Default)
TYPED_SFIELD(sfParentBatchID, UINT256, 36) TYPED_SFIELD(sfParentBatchID, UINT256, 36)
// number (common) // number (common)
@@ -208,6 +210,9 @@ TYPED_SFIELD(sfAssetsMaximum, NUMBER, 3)
TYPED_SFIELD(sfAssetsTotal, NUMBER, 4) TYPED_SFIELD(sfAssetsTotal, NUMBER, 4)
TYPED_SFIELD(sfLossUnrealized, NUMBER, 5) TYPED_SFIELD(sfLossUnrealized, NUMBER, 5)
// 32-bit signed (common)
TYPED_SFIELD(sfWasmReturnCode, INT32, 1)
// currency amount (common) // currency amount (common)
TYPED_SFIELD(sfAmount, AMOUNT, 1) TYPED_SFIELD(sfAmount, AMOUNT, 1)
TYPED_SFIELD(sfBalance, AMOUNT, 2) TYPED_SFIELD(sfBalance, AMOUNT, 2)
@@ -236,7 +241,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)
@@ -278,6 +283,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

@@ -22,31 +22,16 @@
#endif #endif
/** /**
* TRANSACTION(tag, value, name, delegatable, amendments, privileges, fields) * TRANSACTION(tag, value, name, delegatable, amendments, fields)
*
* To ease maintenance, you may replace any unneeded values with "..."
* e.g. #define TRANSACTION(tag, value, name, ...)
* *
* You must define a transactor class in the `ripple` namespace named `name`, * You must define a transactor class in the `ripple` namespace named `name`,
* and include its header alongside the TRANSACTOR definition using this * and include its header in `src/xrpld/app/tx/detail/applySteps.cpp`.
* format:
* #if TRANSACTION_INCLUDE
* # include <xrpld/app/tx/detail/HEADER.h>
* #endif
*
* The `privileges` parameter of the TRANSACTION macro is a bitfield
* defining which operations the transaction can perform.
* The values are defined and used in InvariantCheck.cpp
*/ */
/** This transaction type executes a payment. */ /** This transaction type executes a payment. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Payment.h>
#endif
TRANSACTION(ttPAYMENT, 0, Payment, TRANSACTION(ttPAYMENT, 0, Payment,
Delegation::delegatable, Delegation::delegatable,
uint256{}, uint256{},
createAcct,
({ ({
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported}, {sfAmount, soeREQUIRED, soeMPTSupported},
@@ -60,44 +45,38 @@ TRANSACTION(ttPAYMENT, 0, Payment,
})) }))
/** This transaction type creates an escrow object. */ /** This transaction type creates an escrow object. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Escrow.h>
#endif
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
Delegation::delegatable, Delegation::delegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
{sfDestinationTag, soeOPTIONAL},
{sfAmount, soeREQUIRED, soeMPTSupported}, {sfAmount, soeREQUIRED, soeMPTSupported},
{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. */
TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish,
Delegation::delegatable, Delegation::delegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfOwner, soeREQUIRED}, {sfOwner, soeREQUIRED},
{sfOfferSequence, soeREQUIRED}, {sfOfferSequence, soeREQUIRED},
{sfFulfillment, soeOPTIONAL}, {sfFulfillment, soeOPTIONAL},
{sfCondition, soeOPTIONAL}, {sfCondition, soeOPTIONAL},
{sfCredentialIDs, soeOPTIONAL}, {sfCredentialIDs, soeOPTIONAL},
{sfComputationAllowance, soeOPTIONAL},
})) }))
/** This transaction type adjusts various account settings. */ /** This transaction type adjusts various account settings. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetAccount.h>
#endif
TRANSACTION(ttACCOUNT_SET, 3, AccountSet, TRANSACTION(ttACCOUNT_SET, 3, AccountSet,
Delegation::notDelegatable, Delegation::notDelegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfEmailHash, soeOPTIONAL}, {sfEmailHash, soeOPTIONAL},
{sfWalletLocator, soeOPTIONAL}, {sfWalletLocator, soeOPTIONAL},
@@ -112,26 +91,18 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet,
})) }))
/** This transaction type cancels an existing escrow. */ /** This transaction type cancels an existing escrow. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Escrow.h>
#endif
TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel,
Delegation::delegatable, Delegation::delegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfOwner, soeREQUIRED}, {sfOwner, soeREQUIRED},
{sfOfferSequence, soeREQUIRED}, {sfOfferSequence, soeREQUIRED},
})) }))
/** This transaction type sets or clears an account's "regular key". */ /** This transaction type sets or clears an account's "regular key". */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetRegularKey.h>
#endif
TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey,
Delegation::notDelegatable, Delegation::notDelegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfRegularKey, soeOPTIONAL}, {sfRegularKey, soeOPTIONAL},
})) }))
@@ -139,13 +110,9 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey,
// 6 deprecated // 6 deprecated
/** This transaction type creates an offer to trade one asset for another. */ /** This transaction type creates an offer to trade one asset for another. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateOffer.h>
#endif
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, TRANSACTION(ttOFFER_CREATE, 7, OfferCreate,
Delegation::delegatable, Delegation::delegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfTakerPays, soeREQUIRED}, {sfTakerPays, soeREQUIRED},
{sfTakerGets, soeREQUIRED}, {sfTakerGets, soeREQUIRED},
@@ -155,13 +122,9 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate,
})) }))
/** This transaction type cancels existing offers to trade one asset for another. */ /** This transaction type cancels existing offers to trade one asset for another. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CancelOffer.h>
#endif
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel,
Delegation::delegatable, Delegation::delegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfOfferSequence, soeREQUIRED}, {sfOfferSequence, soeREQUIRED},
})) }))
@@ -169,13 +132,9 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel,
// 9 deprecated // 9 deprecated
/** This transaction type creates a new set of tickets. */ /** This transaction type creates a new set of tickets. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateTicket.h>
#endif
TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, TRANSACTION(ttTICKET_CREATE, 10, TicketCreate,
Delegation::delegatable, Delegation::delegatable,
featureTicketBatch, featureTicketBatch,
noPriv,
({ ({
{sfTicketCount, soeREQUIRED}, {sfTicketCount, soeREQUIRED},
})) }))
@@ -185,26 +144,18 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate,
/** This transaction type modifies the signer list associated with an account. */ /** This transaction type modifies the signer list associated with an account. */
// The SignerEntries are optional because a SignerList is deleted by // The SignerEntries are optional because a SignerList is deleted by
// setting the SignerQuorum to zero and omitting SignerEntries. // setting the SignerQuorum to zero and omitting SignerEntries.
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetSignerList.h>
#endif
TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
Delegation::notDelegatable, Delegation::notDelegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfSignerQuorum, soeREQUIRED}, {sfSignerQuorum, soeREQUIRED},
{sfSignerEntries, soeOPTIONAL}, {sfSignerEntries, soeOPTIONAL},
})) }))
/** This transaction type creates a new unidirectional XRP payment channel. */ /** This transaction type creates a new unidirectional XRP payment channel. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PayChan.h>
#endif
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate,
Delegation::delegatable, Delegation::delegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED}, {sfAmount, soeREQUIRED},
@@ -218,7 +169,6 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate,
TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund,
Delegation::delegatable, Delegation::delegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfChannel, soeREQUIRED}, {sfChannel, soeREQUIRED},
{sfAmount, soeREQUIRED}, {sfAmount, soeREQUIRED},
@@ -229,7 +179,6 @@ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund,
TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim,
Delegation::delegatable, Delegation::delegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfChannel, soeREQUIRED}, {sfChannel, soeREQUIRED},
{sfAmount, soeOPTIONAL}, {sfAmount, soeOPTIONAL},
@@ -240,13 +189,9 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim,
})) }))
/** This transaction type creates a new check. */ /** This transaction type creates a new check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateCheck.h>
#endif
TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
Delegation::delegatable, Delegation::delegatable,
featureChecks, featureChecks,
noPriv,
({ ({
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
{sfSendMax, soeREQUIRED}, {sfSendMax, soeREQUIRED},
@@ -256,13 +201,9 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
})) }))
/** This transaction type cashes an existing check. */ /** This transaction type cashes an existing check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CashCheck.h>
#endif
TRANSACTION(ttCHECK_CASH, 17, CheckCash, TRANSACTION(ttCHECK_CASH, 17, CheckCash,
Delegation::delegatable, Delegation::delegatable,
featureChecks, featureChecks,
noPriv,
({ ({
{sfCheckID, soeREQUIRED}, {sfCheckID, soeREQUIRED},
{sfAmount, soeOPTIONAL}, {sfAmount, soeOPTIONAL},
@@ -270,25 +211,17 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash,
})) }))
/** This transaction type cancels an existing check. */ /** This transaction type cancels an existing check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CancelCheck.h>
#endif
TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel,
Delegation::delegatable, Delegation::delegatable,
featureChecks, featureChecks,
noPriv,
({ ({
{sfCheckID, soeREQUIRED}, {sfCheckID, soeREQUIRED},
})) }))
/** This transaction type grants or revokes authorization to transfer funds. */ /** This transaction type grants or revokes authorization to transfer funds. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DepositPreauth.h>
#endif
TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth,
Delegation::delegatable, Delegation::delegatable,
featureDepositPreauth, featureDepositPreauth,
noPriv,
({ ({
{sfAuthorize, soeOPTIONAL}, {sfAuthorize, soeOPTIONAL},
{sfUnauthorize, soeOPTIONAL}, {sfUnauthorize, soeOPTIONAL},
@@ -297,13 +230,9 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth,
})) }))
/** This transaction type modifies a trustline between two accounts. */ /** This transaction type modifies a trustline between two accounts. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetTrust.h>
#endif
TRANSACTION(ttTRUST_SET, 20, TrustSet, TRANSACTION(ttTRUST_SET, 20, TrustSet,
Delegation::delegatable, Delegation::delegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfLimitAmount, soeOPTIONAL}, {sfLimitAmount, soeOPTIONAL},
{sfQualityIn, soeOPTIONAL}, {sfQualityIn, soeOPTIONAL},
@@ -311,13 +240,9 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet,
})) }))
/** This transaction type deletes an existing account. */ /** This transaction type deletes an existing account. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DeleteAccount.h>
#endif
TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete,
Delegation::notDelegatable, Delegation::notDelegatable,
uint256{}, uint256{},
mustDeleteAcct,
({ ({
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
{sfDestinationTag, soeOPTIONAL}, {sfDestinationTag, soeOPTIONAL},
@@ -327,13 +252,9 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete,
// 22 reserved // 22 reserved
/** This transaction mints a new NFT. */ /** This transaction mints a new NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenMint.h>
#endif
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint,
Delegation::delegatable, Delegation::delegatable,
featureNonFungibleTokensV1, featureNonFungibleTokensV1,
changeNFTCounts,
({ ({
{sfNFTokenTaxon, soeREQUIRED}, {sfNFTokenTaxon, soeREQUIRED},
{sfTransferFee, soeOPTIONAL}, {sfTransferFee, soeOPTIONAL},
@@ -345,26 +266,18 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint,
})) }))
/** This transaction burns (i.e. destroys) an existing NFT. */ /** This transaction burns (i.e. destroys) an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenBurn.h>
#endif
TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn,
Delegation::delegatable, Delegation::delegatable,
featureNonFungibleTokensV1, featureNonFungibleTokensV1,
changeNFTCounts,
({ ({
{sfNFTokenID, soeREQUIRED}, {sfNFTokenID, soeREQUIRED},
{sfOwner, soeOPTIONAL}, {sfOwner, soeOPTIONAL},
})) }))
/** This transaction creates a new offer to buy or sell an NFT. */ /** This transaction creates a new offer to buy or sell an NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenCreateOffer.h>
#endif
TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer,
Delegation::delegatable, Delegation::delegatable,
featureNonFungibleTokensV1, featureNonFungibleTokensV1,
noPriv,
({ ({
{sfNFTokenID, soeREQUIRED}, {sfNFTokenID, soeREQUIRED},
{sfAmount, soeREQUIRED}, {sfAmount, soeREQUIRED},
@@ -374,25 +287,17 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer,
})) }))
/** This transaction cancels an existing offer to buy or sell an existing NFT. */ /** This transaction cancels an existing offer to buy or sell an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenCancelOffer.h>
#endif
TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer,
Delegation::delegatable, Delegation::delegatable,
featureNonFungibleTokensV1, featureNonFungibleTokensV1,
noPriv,
({ ({
{sfNFTokenOffers, soeREQUIRED}, {sfNFTokenOffers, soeREQUIRED},
})) }))
/** This transaction accepts an existing offer to buy or sell an existing NFT. */ /** This transaction accepts an existing offer to buy or sell an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenAcceptOffer.h>
#endif
TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer,
Delegation::delegatable, Delegation::delegatable,
featureNonFungibleTokensV1, featureNonFungibleTokensV1,
noPriv,
({ ({
{sfNFTokenBuyOffer, soeOPTIONAL}, {sfNFTokenBuyOffer, soeOPTIONAL},
{sfNFTokenSellOffer, soeOPTIONAL}, {sfNFTokenSellOffer, soeOPTIONAL},
@@ -400,26 +305,18 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer,
})) }))
/** This transaction claws back issued tokens. */ /** This transaction claws back issued tokens. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Clawback.h>
#endif
TRANSACTION(ttCLAWBACK, 30, Clawback, TRANSACTION(ttCLAWBACK, 30, Clawback,
Delegation::delegatable, Delegation::delegatable,
featureClawback, featureClawback,
noPriv,
({ ({
{sfAmount, soeREQUIRED, soeMPTSupported}, {sfAmount, soeREQUIRED, soeMPTSupported},
{sfHolder, soeOPTIONAL}, {sfHolder, soeOPTIONAL},
})) }))
/** This transaction claws back tokens from an AMM pool. */ /** This transaction claws back tokens from an AMM pool. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMClawback.h>
#endif
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
Delegation::delegatable, Delegation::delegatable,
featureAMMClawback, featureAMMClawback,
mayDeleteAcct | overrideFreeze,
({ ({
{sfHolder, soeREQUIRED}, {sfHolder, soeREQUIRED},
{sfAsset, soeREQUIRED}, {sfAsset, soeREQUIRED},
@@ -428,13 +325,9 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
})) }))
/** This transaction type creates an AMM instance */ /** This transaction type creates an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMCreate.h>
#endif
TRANSACTION(ttAMM_CREATE, 35, AMMCreate, TRANSACTION(ttAMM_CREATE, 35, AMMCreate,
Delegation::delegatable, Delegation::delegatable,
featureAMM, featureAMM,
createPseudoAcct,
({ ({
{sfAmount, soeREQUIRED}, {sfAmount, soeREQUIRED},
{sfAmount2, soeREQUIRED}, {sfAmount2, soeREQUIRED},
@@ -442,13 +335,9 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate,
})) }))
/** This transaction type deposits into an AMM instance */ /** This transaction type deposits into an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMDeposit.h>
#endif
TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
Delegation::delegatable, Delegation::delegatable,
featureAMM, featureAMM,
noPriv,
({ ({
{sfAsset, soeREQUIRED}, {sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED}, {sfAsset2, soeREQUIRED},
@@ -460,13 +349,9 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
})) }))
/** This transaction type withdraws from an AMM instance */ /** This transaction type withdraws from an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMWithdraw.h>
#endif
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw,
Delegation::delegatable, Delegation::delegatable,
featureAMM, featureAMM,
mayDeleteAcct,
({ ({
{sfAsset, soeREQUIRED}, {sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED}, {sfAsset2, soeREQUIRED},
@@ -477,13 +362,9 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw,
})) }))
/** This transaction type votes for the trading fee */ /** This transaction type votes for the trading fee */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMVote.h>
#endif
TRANSACTION(ttAMM_VOTE, 38, AMMVote, TRANSACTION(ttAMM_VOTE, 38, AMMVote,
Delegation::delegatable, Delegation::delegatable,
featureAMM, featureAMM,
noPriv,
({ ({
{sfAsset, soeREQUIRED}, {sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED}, {sfAsset2, soeREQUIRED},
@@ -491,13 +372,9 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote,
})) }))
/** This transaction type bids for the auction slot */ /** This transaction type bids for the auction slot */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMBid.h>
#endif
TRANSACTION(ttAMM_BID, 39, AMMBid, TRANSACTION(ttAMM_BID, 39, AMMBid,
Delegation::delegatable, Delegation::delegatable,
featureAMM, featureAMM,
noPriv,
({ ({
{sfAsset, soeREQUIRED}, {sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED}, {sfAsset2, soeREQUIRED},
@@ -507,26 +384,18 @@ TRANSACTION(ttAMM_BID, 39, AMMBid,
})) }))
/** This transaction type deletes AMM in the empty state */ /** This transaction type deletes AMM in the empty state */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMDelete.h>
#endif
TRANSACTION(ttAMM_DELETE, 40, AMMDelete, TRANSACTION(ttAMM_DELETE, 40, AMMDelete,
Delegation::delegatable, Delegation::delegatable,
featureAMM, featureAMM,
mustDeleteAcct,
({ ({
{sfAsset, soeREQUIRED}, {sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED}, {sfAsset2, soeREQUIRED},
})) }))
/** This transactions creates a crosschain sequence number */ /** This transactions creates a crosschain sequence number */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/XChainBridge.h>
#endif
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID,
Delegation::delegatable, Delegation::delegatable,
featureXChainBridge, featureXChainBridge,
noPriv,
({ ({
{sfXChainBridge, soeREQUIRED}, {sfXChainBridge, soeREQUIRED},
{sfSignatureReward, soeREQUIRED}, {sfSignatureReward, soeREQUIRED},
@@ -537,7 +406,6 @@ TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID,
TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit,
Delegation::delegatable, Delegation::delegatable,
featureXChainBridge, featureXChainBridge,
noPriv,
({ ({
{sfXChainBridge, soeREQUIRED}, {sfXChainBridge, soeREQUIRED},
{sfXChainClaimID, soeREQUIRED}, {sfXChainClaimID, soeREQUIRED},
@@ -549,7 +417,6 @@ TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit,
TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim,
Delegation::delegatable, Delegation::delegatable,
featureXChainBridge, featureXChainBridge,
noPriv,
({ ({
{sfXChainBridge, soeREQUIRED}, {sfXChainBridge, soeREQUIRED},
{sfXChainClaimID, soeREQUIRED}, {sfXChainClaimID, soeREQUIRED},
@@ -562,7 +429,6 @@ TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim,
TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit,
Delegation::delegatable, Delegation::delegatable,
featureXChainBridge, featureXChainBridge,
noPriv,
({ ({
{sfXChainBridge, soeREQUIRED}, {sfXChainBridge, soeREQUIRED},
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
@@ -574,7 +440,6 @@ TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit,
TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation,
Delegation::delegatable, Delegation::delegatable,
featureXChainBridge, featureXChainBridge,
createAcct,
({ ({
{sfXChainBridge, soeREQUIRED}, {sfXChainBridge, soeREQUIRED},
@@ -591,11 +456,9 @@ TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation,
})) }))
/** This transaction adds an attestation to an account */ /** This transaction adds an attestation to an account */
TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateAttestation,
XChainAddAccountCreateAttestation,
Delegation::delegatable, Delegation::delegatable,
featureXChainBridge, featureXChainBridge,
createAcct,
({ ({
{sfXChainBridge, soeREQUIRED}, {sfXChainBridge, soeREQUIRED},
@@ -616,7 +479,6 @@ TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46,
TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge,
Delegation::delegatable, Delegation::delegatable,
featureXChainBridge, featureXChainBridge,
noPriv,
({ ({
{sfXChainBridge, soeREQUIRED}, {sfXChainBridge, soeREQUIRED},
{sfSignatureReward, soeOPTIONAL}, {sfSignatureReward, soeOPTIONAL},
@@ -627,7 +489,6 @@ TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge,
TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge,
Delegation::delegatable, Delegation::delegatable,
featureXChainBridge, featureXChainBridge,
noPriv,
({ ({
{sfXChainBridge, soeREQUIRED}, {sfXChainBridge, soeREQUIRED},
{sfSignatureReward, soeREQUIRED}, {sfSignatureReward, soeREQUIRED},
@@ -635,13 +496,9 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge,
})) }))
/** This transaction type creates or updates a DID */ /** This transaction type creates or updates a DID */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DID.h>
#endif
TRANSACTION(ttDID_SET, 49, DIDSet, TRANSACTION(ttDID_SET, 49, DIDSet,
Delegation::delegatable, Delegation::delegatable,
featureDID, featureDID,
noPriv,
({ ({
{sfDIDDocument, soeOPTIONAL}, {sfDIDDocument, soeOPTIONAL},
{sfURI, soeOPTIONAL}, {sfURI, soeOPTIONAL},
@@ -652,17 +509,12 @@ TRANSACTION(ttDID_SET, 49, DIDSet,
TRANSACTION(ttDID_DELETE, 50, DIDDelete, TRANSACTION(ttDID_DELETE, 50, DIDDelete,
Delegation::delegatable, Delegation::delegatable,
featureDID, featureDID,
noPriv,
({})) ({}))
/** This transaction type creates an Oracle instance */ /** This transaction type creates an Oracle instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetOracle.h>
#endif
TRANSACTION(ttORACLE_SET, 51, OracleSet, TRANSACTION(ttORACLE_SET, 51, OracleSet,
Delegation::delegatable, Delegation::delegatable,
featurePriceOracle, featurePriceOracle,
noPriv,
({ ({
{sfOracleDocumentID, soeREQUIRED}, {sfOracleDocumentID, soeREQUIRED},
{sfProvider, soeOPTIONAL}, {sfProvider, soeOPTIONAL},
@@ -673,97 +525,65 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet,
})) }))
/** This transaction type deletes an Oracle instance */ /** This transaction type deletes an Oracle instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DeleteOracle.h>
#endif
TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, TRANSACTION(ttORACLE_DELETE, 52, OracleDelete,
Delegation::delegatable, Delegation::delegatable,
featurePriceOracle, featurePriceOracle,
noPriv,
({ ({
{sfOracleDocumentID, soeREQUIRED}, {sfOracleDocumentID, soeREQUIRED},
})) }))
/** This transaction type fixes a problem in the ledger state */ /** This transaction type fixes a problem in the ledger state */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LedgerStateFix.h>
#endif
TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix,
Delegation::delegatable, Delegation::delegatable,
fixNFTokenPageLinks, fixNFTokenPageLinks,
noPriv,
({ ({
{sfLedgerFixType, soeREQUIRED}, {sfLedgerFixType, soeREQUIRED},
{sfOwner, soeOPTIONAL}, {sfOwner, soeOPTIONAL},
})) }))
/** This transaction type creates a MPTokensIssuance instance */ /** This transaction type creates a MPTokensIssuance instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate,
Delegation::delegatable, Delegation::delegatable,
featureMPTokensV1, featureMPTokensV1,
createMPTIssuance,
({ ({
{sfAssetScale, soeOPTIONAL}, {sfAssetScale, soeOPTIONAL},
{sfTransferFee, soeOPTIONAL}, {sfTransferFee, soeOPTIONAL},
{sfMaximumAmount, soeOPTIONAL}, {sfMaximumAmount, soeOPTIONAL},
{sfMPTokenMetadata, soeOPTIONAL}, {sfMPTokenMetadata, soeOPTIONAL},
{sfDomainID, soeOPTIONAL}, {sfDomainID, soeOPTIONAL},
{sfMutableFlags, soeOPTIONAL},
})) }))
/** This transaction type destroys a MPTokensIssuance instance */ /** This transaction type destroys a MPTokensIssuance instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy,
Delegation::delegatable, Delegation::delegatable,
featureMPTokensV1, featureMPTokensV1,
destroyMPTIssuance,
({ ({
{sfMPTokenIssuanceID, soeREQUIRED}, {sfMPTokenIssuanceID, soeREQUIRED},
})) }))
/** This transaction type sets flags on a MPTokensIssuance or MPToken instance */ /** This transaction type sets flags on a MPTokensIssuance or MPToken instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
Delegation::delegatable, Delegation::delegatable,
featureMPTokensV1, featureMPTokensV1,
noPriv,
({ ({
{sfMPTokenIssuanceID, soeREQUIRED}, {sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeOPTIONAL}, {sfHolder, soeOPTIONAL},
{sfDomainID, soeOPTIONAL}, {sfDomainID, soeOPTIONAL},
{sfMPTokenMetadata, soeOPTIONAL},
{sfTransferFee, soeOPTIONAL},
{sfMutableFlags, soeOPTIONAL},
})) }))
/** This transaction type authorizes a MPToken instance */ /** This transaction type authorizes a MPToken instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenAuthorize.h>
#endif
TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize,
Delegation::delegatable, Delegation::delegatable,
featureMPTokensV1, featureMPTokensV1,
mustAuthorizeMPT,
({ ({
{sfMPTokenIssuanceID, soeREQUIRED}, {sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeOPTIONAL}, {sfHolder, soeOPTIONAL},
})) }))
/** This transaction type create an Credential instance */ /** This transaction type create an Credential instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Credentials.h>
#endif
TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate,
Delegation::delegatable, Delegation::delegatable,
featureCredentials, featureCredentials,
noPriv,
({ ({
{sfSubject, soeREQUIRED}, {sfSubject, soeREQUIRED},
{sfCredentialType, soeREQUIRED}, {sfCredentialType, soeREQUIRED},
@@ -775,7 +595,6 @@ TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate,
TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept,
Delegation::delegatable, Delegation::delegatable,
featureCredentials, featureCredentials,
noPriv,
({ ({
{sfIssuer, soeREQUIRED}, {sfIssuer, soeREQUIRED},
{sfCredentialType, soeREQUIRED}, {sfCredentialType, soeREQUIRED},
@@ -785,7 +604,6 @@ TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept,
TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete,
Delegation::delegatable, Delegation::delegatable,
featureCredentials, featureCredentials,
noPriv,
({ ({
{sfSubject, soeOPTIONAL}, {sfSubject, soeOPTIONAL},
{sfIssuer, soeOPTIONAL}, {sfIssuer, soeOPTIONAL},
@@ -793,13 +611,9 @@ TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete,
})) }))
/** This transaction type modify a NFToken */ /** This transaction type modify a NFToken */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenModify.h>
#endif
TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify,
Delegation::delegatable, Delegation::delegatable,
featureDynamicNFT, featureDynamicNFT,
noPriv,
({ ({
{sfNFTokenID, soeREQUIRED}, {sfNFTokenID, soeREQUIRED},
{sfOwner, soeOPTIONAL}, {sfOwner, soeOPTIONAL},
@@ -807,51 +621,35 @@ TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify,
})) }))
/** This transaction type creates or modifies a Permissioned Domain */ /** This transaction type creates or modifies a Permissioned Domain */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PermissionedDomainSet.h>
#endif
TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet,
Delegation::delegatable, Delegation::delegatable,
featurePermissionedDomains, featurePermissionedDomains,
noPriv,
({ ({
{sfDomainID, soeOPTIONAL}, {sfDomainID, soeOPTIONAL},
{sfAcceptedCredentials, soeREQUIRED}, {sfAcceptedCredentials, soeREQUIRED},
})) }))
/** This transaction type deletes a Permissioned Domain */ /** This transaction type deletes a Permissioned Domain */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PermissionedDomainDelete.h>
#endif
TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete,
Delegation::delegatable, Delegation::delegatable,
featurePermissionedDomains, featurePermissionedDomains,
noPriv,
({ ({
{sfDomainID, soeREQUIRED}, {sfDomainID, soeREQUIRED},
})) }))
/** This transaction type delegates authorized account specified permissions */ /** This transaction type delegates authorized account specified permissions */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DelegateSet.h>
#endif
TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, TRANSACTION(ttDELEGATE_SET, 64, DelegateSet,
Delegation::notDelegatable, Delegation::notDelegatable,
featurePermissionDelegation, featurePermissionDelegation,
noPriv,
({ ({
{sfAuthorize, soeREQUIRED}, {sfAuthorize, soeREQUIRED},
{sfPermissions, soeREQUIRED}, {sfPermissions, soeREQUIRED},
})) }))
/** This transaction creates a single asset vault. */ /** This transaction creates a single asset vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultCreate.h>
#endif
TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
Delegation::delegatable, Delegation::delegatable,
featureSingleAssetVault, featureSingleAssetVault,
createPseudoAcct | createMPTIssuance,
({ ({
{sfAsset, soeREQUIRED, soeMPTSupported}, {sfAsset, soeREQUIRED, soeMPTSupported},
{sfAssetsMaximum, soeOPTIONAL}, {sfAssetsMaximum, soeOPTIONAL},
@@ -863,13 +661,9 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
})) }))
/** This transaction updates a single asset vault. */ /** This transaction updates a single asset vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultSet.h>
#endif
TRANSACTION(ttVAULT_SET, 66, VaultSet, TRANSACTION(ttVAULT_SET, 66, VaultSet,
Delegation::delegatable, Delegation::delegatable,
featureSingleAssetVault, featureSingleAssetVault,
noPriv,
({ ({
{sfVaultID, soeREQUIRED}, {sfVaultID, soeREQUIRED},
{sfAssetsMaximum, soeOPTIONAL}, {sfAssetsMaximum, soeOPTIONAL},
@@ -878,38 +672,26 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet,
})) }))
/** This transaction deletes a single asset vault. */ /** This transaction deletes a single asset vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultDelete.h>
#endif
TRANSACTION(ttVAULT_DELETE, 67, VaultDelete, TRANSACTION(ttVAULT_DELETE, 67, VaultDelete,
Delegation::delegatable, Delegation::delegatable,
featureSingleAssetVault, featureSingleAssetVault,
mustDeleteAcct | destroyMPTIssuance,
({ ({
{sfVaultID, soeREQUIRED}, {sfVaultID, soeREQUIRED},
})) }))
/** This transaction trades assets for shares with a vault. */ /** This transaction trades assets for shares with a vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultDeposit.h>
#endif
TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit, TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit,
Delegation::delegatable, Delegation::delegatable,
featureSingleAssetVault, featureSingleAssetVault,
mayAuthorizeMPT,
({ ({
{sfVaultID, soeREQUIRED}, {sfVaultID, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported}, {sfAmount, soeREQUIRED, soeMPTSupported},
})) }))
/** This transaction trades shares for assets with a vault. */ /** This transaction trades shares for assets with a vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultWithdraw.h>
#endif
TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw,
Delegation::delegatable, Delegation::delegatable,
featureSingleAssetVault, featureSingleAssetVault,
mayDeleteMPT,
({ ({
{sfVaultID, soeREQUIRED}, {sfVaultID, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported}, {sfAmount, soeREQUIRED, soeMPTSupported},
@@ -918,13 +700,9 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw,
})) }))
/** This transaction claws back tokens from a vault. */ /** This transaction claws back tokens from a vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultClawback.h>
#endif
TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback,
Delegation::delegatable, Delegation::delegatable,
featureSingleAssetVault, featureSingleAssetVault,
mayDeleteMPT,
({ ({
{sfVaultID, soeREQUIRED}, {sfVaultID, soeREQUIRED},
{sfHolder, soeREQUIRED}, {sfHolder, soeREQUIRED},
@@ -932,13 +710,9 @@ TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback,
})) }))
/** This transaction type batches together transactions. */ /** This transaction type batches together transactions. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Batch.h>
#endif
TRANSACTION(ttBATCH, 71, Batch, TRANSACTION(ttBATCH, 71, Batch,
Delegation::notDelegatable, Delegation::notDelegatable,
featureBatch, featureBatch,
noPriv,
({ ({
{sfRawTransactions, soeREQUIRED}, {sfRawTransactions, soeREQUIRED},
{sfBatchSigners, soeOPTIONAL}, {sfBatchSigners, soeOPTIONAL},
@@ -948,13 +722,9 @@ TRANSACTION(ttBATCH, 71, Batch,
For details, see: https://xrpl.org/amendments.html For details, see: https://xrpl.org/amendments.html
*/ */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Change.h>
#endif
TRANSACTION(ttAMENDMENT, 100, EnableAmendment, TRANSACTION(ttAMENDMENT, 100, EnableAmendment,
Delegation::notDelegatable, Delegation::notDelegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfLedgerSequence, soeREQUIRED}, {sfLedgerSequence, soeREQUIRED},
{sfAmendment, soeREQUIRED}, {sfAmendment, soeREQUIRED},
@@ -966,7 +736,6 @@ TRANSACTION(ttAMENDMENT, 100, EnableAmendment,
TRANSACTION(ttFEE, 101, SetFee, TRANSACTION(ttFEE, 101, SetFee,
Delegation::notDelegatable, Delegation::notDelegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfLedgerSequence, soeOPTIONAL}, {sfLedgerSequence, soeOPTIONAL},
// Old version uses raw numbers // Old version uses raw numbers
@@ -987,7 +756,6 @@ TRANSACTION(ttFEE, 101, SetFee,
TRANSACTION(ttUNL_MODIFY, 102, UNLModify, TRANSACTION(ttUNL_MODIFY, 102, UNLModify,
Delegation::notDelegatable, Delegation::notDelegatable,
uint256{}, uint256{},
noPriv,
({ ({
{sfUNLModifyDisabling, soeREQUIRED}, {sfUNLModifyDisabling, soeREQUIRED},
{sfLedgerSequence, soeREQUIRED}, {sfLedgerSequence, soeREQUIRED},

View File

@@ -17,681 +17,98 @@
*/ */
//============================================================================== //==============================================================================
#include <xrpl/basics/Log.h> #ifndef RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
#include <xrpl/beast/utility/instrumentation.h> #define RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/detail/ApplyStateTable.h> #include <xrpld/ledger/OpenView.h>
#include <xrpl/protocol/Feature.h> #include <xrpld/ledger/detail/ApplyViewBase.h>
#include <xrpl/protocol/st.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
namespace ripple { namespace ripple {
namespace detail {
void /** Editable, discardable view that can build metadata for one tx.
ApplyStateTable::apply(RawView& to) const
Iteration of the tx map is delegated to the base.
@note Presented as ApplyView to clients.
*/
class ApplyViewImpl final : public detail::ApplyViewBase
{ {
to.rawDestroyXRP(dropsDestroyed_); public:
for (auto const& item : items_) ApplyViewImpl() = delete;
ApplyViewImpl(ApplyViewImpl const&) = delete;
ApplyViewImpl&
operator=(ApplyViewImpl&&) = delete;
ApplyViewImpl&
operator=(ApplyViewImpl const&) = delete;
ApplyViewImpl(ApplyViewImpl&&) = default;
ApplyViewImpl(ReadView const* base, ApplyFlags flags);
/** Apply the transaction.
After a call to `apply`, the only valid
operation on this object is to call the
destructor.
*/
std::optional<TxMeta>
apply(
OpenView& to,
STTx const& tx,
TER ter,
std::optional<uint256> parentBatchId,
bool isDryRun,
beast::Journal j);
/** Set the amount of currency delivered.
This value is used when generating metadata
for payments, to set the DeliveredAmount field.
If the amount is not specified, the field is
excluded from the resulting metadata.
*/
void
deliver(STAmount const& amount)
{ {
auto const& sle = item.second.second; deliver_ = amount;
switch (item.second.first)
{
case Action::cache:
break;
case Action::erase:
to.rawErase(sle);
break;
case Action::insert:
to.rawInsert(sle);
break;
case Action::modify:
to.rawReplace(sle);
break;
};
}
}
std::size_t
ApplyStateTable::size() const
{
std::size_t ret = 0;
for (auto& item : items_)
{
switch (item.second.first)
{
case Action::erase:
case Action::insert:
case Action::modify:
++ret;
default:
break;
}
}
return ret;
}
void
ApplyStateTable::visit(
ReadView const& to,
std::function<void(
uint256 const& key,
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)> const& func) const
{
for (auto& item : items_)
{
switch (item.second.first)
{
case Action::erase:
func(
item.first,
true,
to.read(keylet::unchecked(item.first)),
item.second.second);
break;
case Action::insert:
func(item.first, false, nullptr, item.second.second);
break;
case Action::modify:
func(
item.first,
false,
to.read(keylet::unchecked(item.first)),
item.second.second);
break;
default:
break;
}
}
}
std::optional<TxMeta>
ApplyStateTable::apply(
OpenView& to,
STTx const& tx,
TER ter,
std::optional<STAmount> const& deliver,
std::optional<uint256 const> const& parentBatchId,
bool isDryRun,
beast::Journal j)
{
// Build metadata and insert
auto const sTx = std::make_shared<Serializer>();
tx.add(*sTx);
std::shared_ptr<Serializer> sMeta;
std::optional<TxMeta> metadata;
if (!to.open() || isDryRun)
{
TxMeta meta(tx.getTransactionID(), to.seq(), parentBatchId);
if (deliver)
meta.setDeliveredAmount(*deliver);
Mods newMod;
for (auto& item : items_)
{
SField const* type;
switch (item.second.first)
{
default:
case Action::cache:
continue;
case Action::erase:
type = &sfDeletedNode;
break;
case Action::insert:
type = &sfCreatedNode;
break;
case Action::modify:
type = &sfModifiedNode;
break;
}
auto const origNode = to.read(keylet::unchecked(item.first));
auto curNode = item.second.second;
if ((type == &sfModifiedNode) && (*curNode == *origNode))
continue;
std::uint16_t nodeType = curNode
? curNode->getFieldU16(sfLedgerEntryType)
: origNode->getFieldU16(sfLedgerEntryType);
meta.setAffectedNode(item.first, *type, nodeType);
if (type == &sfDeletedNode)
{
XRPL_ASSERT(
origNode && curNode,
"ripple::detail::ApplyStateTable::apply : valid nodes for "
"deletion");
threadOwners(to, meta, origNode, newMod, j);
STObject prevs(sfPreviousFields);
for (auto const& obj : *origNode)
{
// go through the original node for
// modified fields saved on modification
if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) &&
!curNode->hasMatchingEntry(obj))
prevs.emplace_back(obj);
}
if (!prevs.empty())
meta.getAffectedNode(item.first)
.emplace_back(std::move(prevs));
STObject finals(sfFinalFields);
for (auto const& obj : *curNode)
{
// go through the final node for final fields
if (obj.getFName().shouldMeta(
SField::sMD_Always | SField::sMD_DeleteFinal))
finals.emplace_back(obj);
}
if (!finals.empty())
meta.getAffectedNode(item.first)
.emplace_back(std::move(finals));
}
else if (type == &sfModifiedNode)
{
XRPL_ASSERT(
curNode && origNode,
"ripple::detail::ApplyStateTable::apply : valid nodes for "
"modification");
if (curNode->isThreadedType(
to.rules())) // thread transaction to node
// item modified
threadItem(meta, curNode);
STObject prevs(sfPreviousFields);
for (auto const& obj : *origNode)
{
// search the original node for values saved on modify
if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) &&
!curNode->hasMatchingEntry(obj))
prevs.emplace_back(obj);
}
if (!prevs.empty())
meta.getAffectedNode(item.first)
.emplace_back(std::move(prevs));
STObject finals(sfFinalFields);
for (auto const& obj : *curNode)
{
// search the final node for values saved always
if (obj.getFName().shouldMeta(
SField::sMD_Always | SField::sMD_ChangeNew))
finals.emplace_back(obj);
}
if (!finals.empty())
meta.getAffectedNode(item.first)
.emplace_back(std::move(finals));
}
else if (type == &sfCreatedNode) // if created, thread to owner(s)
{
XRPL_ASSERT(
curNode && !origNode,
"ripple::detail::ApplyStateTable::apply : valid nodes for "
"creation");
threadOwners(to, meta, curNode, newMod, j);
if (curNode->isThreadedType(
to.rules())) // always thread to self
threadItem(meta, curNode);
STObject news(sfNewFields);
for (auto const& obj : *curNode)
{
// save non-default values
if (!obj.isDefault() &&
obj.getFName().shouldMeta(
SField::sMD_Create | SField::sMD_Always))
news.emplace_back(obj);
}
if (!news.empty())
meta.getAffectedNode(item.first)
.emplace_back(std::move(news));
}
else
{
UNREACHABLE(
"ripple::detail::ApplyStateTable::apply : unsupported "
"operation type");
}
}
if (!isDryRun)
{
// add any new modified nodes to the modification set
for (auto const& mod : newMod)
to.rawReplace(mod.second);
}
sMeta = std::make_shared<Serializer>();
meta.addRaw(*sMeta, ter, to.txCount());
// VFALCO For diagnostics do we want to show
// metadata even when the base view is open?
JLOG(j.trace()) << "metadata " << meta.getJson(JsonOptions::none);
metadata = meta;
} }
if (!isDryRun) void
setGasUsed(std::optional<std::uint32_t> const gasUsed)
{ {
to.rawTxInsert(tx.getTransactionID(), sTx, sMeta); gasUsed_ = gasUsed;
apply(to);
} }
return metadata;
}
//--- void
setWasmReturnCode(std::int32_t const wasmReturnCode)
bool
ApplyStateTable::exists(ReadView const& base, Keylet const& k) const
{
auto const iter = items_.find(k.key);
if (iter == items_.end())
return base.exists(k);
auto const& item = iter->second;
auto const& sle = item.second;
switch (item.first)
{ {
case Action::erase: wasmReturnCode_ = wasmReturnCode;
return false;
case Action::cache:
case Action::insert:
case Action::modify:
break;
} }
if (!k.check(*sle))
return false;
return true;
}
auto /** Get the number of modified entries
ApplyStateTable::succ( */
ReadView const& base, std::size_t
key_type const& key, size();
std::optional<key_type> const& last) const -> std::optional<key_type>
{
std::optional<key_type> next = key;
items_t::const_iterator iter;
// Find base successor that is
// not also deleted in our list
do
{
next = base.succ(*next, last);
if (!next)
break;
iter = items_.find(*next);
} while (iter != items_.end() && iter->second.first == Action::erase);
// Find non-deleted successor in our list
for (iter = items_.upper_bound(key); iter != items_.end(); ++iter)
{
if (iter->second.first != Action::erase)
{
// Found both, return the lower key
if (!next || next > iter->first)
next = iter->first;
break;
}
}
// Nothing in our list, return
// what we got from the parent.
if (last && next >= last)
return std::nullopt;
return next;
}
std::shared_ptr<SLE const> /** Visit modified entries
ApplyStateTable::read(ReadView const& base, Keylet const& k) const */
{ void
auto const iter = items_.find(k.key); visit(
if (iter == items_.end()) OpenView& target,
return base.read(k); std::function<void(
auto const& item = iter->second; uint256 const& key,
auto const& sle = item.second; bool isDelete,
switch (item.first) std::shared_ptr<SLE const> const& before,
{ std::shared_ptr<SLE const> const& after)> const& func);
case Action::erase:
return nullptr;
case Action::cache:
case Action::insert:
case Action::modify:
break;
};
if (!k.check(*sle))
return nullptr;
return sle;
}
std::shared_ptr<SLE> private:
ApplyStateTable::peek(ReadView const& base, Keylet const& k) std::optional<STAmount> deliver_;
{ std::optional<std::uint32_t> gasUsed_;
auto iter = items_.lower_bound(k.key); std::optional<std::int32_t> wasmReturnCode_;
if (iter == items_.end() || iter->first != k.key) };
{
auto const sle = base.read(k);
if (!sle)
return nullptr;
// Make our own copy
using namespace std;
iter = items_.emplace_hint(
iter,
piecewise_construct,
forward_as_tuple(sle->key()),
forward_as_tuple(Action::cache, make_shared<SLE>(*sle)));
return iter->second.second;
}
auto const& item = iter->second;
auto const& sle = item.second;
switch (item.first)
{
case Action::erase:
return nullptr;
case Action::cache:
case Action::insert:
case Action::modify:
break;
};
if (!k.check(*sle))
return nullptr;
return sle;
}
void
ApplyStateTable::erase(ReadView const& base, std::shared_ptr<SLE> const& sle)
{
auto const iter = items_.find(sle->key());
if (iter == items_.end())
LogicError("ApplyStateTable::erase: missing key");
auto& item = iter->second;
if (item.second != sle)
LogicError("ApplyStateTable::erase: unknown SLE");
switch (item.first)
{
case Action::erase:
LogicError("ApplyStateTable::erase: double erase");
break;
case Action::insert:
items_.erase(iter);
break;
case Action::cache:
case Action::modify:
item.first = Action::erase;
break;
}
}
void
ApplyStateTable::rawErase(ReadView const& base, std::shared_ptr<SLE> const& sle)
{
using namespace std;
auto const result = items_.emplace(
piecewise_construct,
forward_as_tuple(sle->key()),
forward_as_tuple(Action::erase, sle));
if (result.second)
return;
auto& item = result.first->second;
switch (item.first)
{
case Action::erase:
LogicError("ApplyStateTable::rawErase: double erase");
break;
case Action::insert:
items_.erase(result.first);
break;
case Action::cache:
case Action::modify:
item.first = Action::erase;
item.second = sle;
break;
}
}
void
ApplyStateTable::insert(ReadView const& base, std::shared_ptr<SLE> const& sle)
{
auto const iter = items_.lower_bound(sle->key());
if (iter == items_.end() || iter->first != sle->key())
{
using namespace std;
items_.emplace_hint(
iter,
piecewise_construct,
forward_as_tuple(sle->key()),
forward_as_tuple(Action::insert, sle));
return;
}
auto& item = iter->second;
switch (item.first)
{
case Action::cache:
LogicError("ApplyStateTable::insert: already cached");
case Action::insert:
LogicError("ApplyStateTable::insert: already inserted");
case Action::modify:
LogicError("ApplyStateTable::insert: already modified");
case Action::erase:
break;
}
item.first = Action::modify;
item.second = sle;
}
void
ApplyStateTable::replace(ReadView const& base, std::shared_ptr<SLE> const& sle)
{
auto const iter = items_.lower_bound(sle->key());
if (iter == items_.end() || iter->first != sle->key())
{
using namespace std;
items_.emplace_hint(
iter,
piecewise_construct,
forward_as_tuple(sle->key()),
forward_as_tuple(Action::modify, sle));
return;
}
auto& item = iter->second;
switch (item.first)
{
case Action::erase:
LogicError("ApplyStateTable::replace: already erased");
case Action::cache:
item.first = Action::modify;
break;
case Action::insert:
case Action::modify:
break;
}
item.second = sle;
}
void
ApplyStateTable::update(ReadView const& base, std::shared_ptr<SLE> const& sle)
{
auto const iter = items_.find(sle->key());
if (iter == items_.end())
LogicError("ApplyStateTable::update: missing key");
auto& item = iter->second;
if (item.second != sle)
LogicError("ApplyStateTable::update: unknown SLE");
switch (item.first)
{
case Action::erase:
LogicError("ApplyStateTable::update: erased");
break;
case Action::cache:
item.first = Action::modify;
break;
case Action::insert:
case Action::modify:
break;
};
}
void
ApplyStateTable::destroyXRP(XRPAmount const& fee)
{
dropsDestroyed_ += fee;
}
//------------------------------------------------------------------------------
// Insert this transaction to the SLE's threading list
void
ApplyStateTable::threadItem(TxMeta& meta, std::shared_ptr<SLE> const& sle)
{
key_type prevTxID;
LedgerIndex prevLgrID;
if (!sle->thread(meta.getTxID(), meta.getLgrSeq(), prevTxID, prevLgrID))
return;
if (!prevTxID.isZero())
{
auto& node = meta.getAffectedNode(sle, sfModifiedNode);
if (node.getFieldIndex(sfPreviousTxnID) == -1)
{
XRPL_ASSERT(
node.getFieldIndex(sfPreviousTxnLgrSeq) == -1,
"ripple::ApplyStateTable::threadItem : previous ledger is not "
"set");
node.setFieldH256(sfPreviousTxnID, prevTxID);
node.setFieldU32(sfPreviousTxnLgrSeq, prevLgrID);
}
XRPL_ASSERT(
node.getFieldH256(sfPreviousTxnID) == prevTxID,
"ripple::ApplyStateTable::threadItem : previous transaction is a "
"match");
XRPL_ASSERT(
node.getFieldU32(sfPreviousTxnLgrSeq) == prevLgrID,
"ripple::ApplyStateTable::threadItem : previous ledger is a match");
}
}
std::shared_ptr<SLE>
ApplyStateTable::getForMod(
ReadView const& base,
key_type const& key,
Mods& mods,
beast::Journal j)
{
{
auto miter = mods.find(key);
if (miter != mods.end())
{
XRPL_ASSERT(
miter->second,
"ripple::ApplyStateTable::getForMod : non-null result");
return miter->second;
}
}
{
auto iter = items_.find(key);
if (iter != items_.end())
{
auto const& item = iter->second;
if (item.first == Action::erase)
{
// The Destination of an Escrow or a PayChannel may have been
// deleted. In that case the account we're threading to will
// not be found and it is appropriate to return a nullptr.
JLOG(j.warn()) << "Trying to thread to deleted node";
return nullptr;
}
if (item.first != Action::cache)
return item.second;
// If it's only cached, then the node is being modified only by
// metadata; fall through and track it in the mods table.
}
}
auto c = base.read(keylet::unchecked(key));
if (!c)
{
// The Destination of an Escrow or a PayChannel may have been
// deleted. In that case the account we're threading to will
// not be found and it is appropriate to return a nullptr.
JLOG(j.warn()) << "ApplyStateTable::getForMod: key not found";
return nullptr;
}
auto sle = std::make_shared<SLE>(*c);
mods.emplace(key, sle);
return sle;
}
void
ApplyStateTable::threadTx(
ReadView const& base,
TxMeta& meta,
AccountID const& to,
Mods& mods,
beast::Journal j)
{
auto const sle = getForMod(base, keylet::account(to).key, mods, j);
if (!sle)
{
// The Destination of an Escrow or PayChannel may have been deleted.
// In that case the account we are threading to will not be found.
// So this logging is just a warning.
JLOG(j.warn()) << "Threading to non-existent account: " << toBase58(to);
return;
}
// threadItem only applied to AccountRoot
XRPL_ASSERT(
sle->isThreadedType(base.rules()),
"ripple::ApplyStateTable::threadTx : SLE is threaded");
threadItem(meta, sle);
}
void
ApplyStateTable::threadOwners(
ReadView const& base,
TxMeta& meta,
std::shared_ptr<SLE const> const& sle,
Mods& mods,
beast::Journal j)
{
LedgerEntryType const ledgerType{sle->getType()};
switch (ledgerType)
{
case ltACCOUNT_ROOT: {
// Nothing to do
break;
}
case ltRIPPLE_STATE: {
threadTx(base, meta, (*sle)[sfLowLimit].getIssuer(), mods, j);
threadTx(base, meta, (*sle)[sfHighLimit].getIssuer(), mods, j);
break;
}
default: {
// If sfAccount is present, thread to that account
if (auto const optSleAcct{(*sle)[~sfAccount]})
threadTx(base, meta, *optSleAcct, mods, j);
// Don't thread a check's sfDestination unless the amendment is
// enabled
if (ledgerType == ltCHECK &&
!base.rules().enabled(fixCheckThreading))
break;
// If sfDestination is present, thread to that account
if (auto const optSleDest{(*sle)[~sfDestination]})
threadTx(base, meta, *optSleDest, mods, j);
}
}
}
} // namespace detail
} // namespace ripple } // namespace ripple
#endif

View File

@@ -17,43 +17,98 @@
*/ */
//============================================================================== //==============================================================================
#include <xrpl/ledger/ApplyViewImpl.h> #ifndef RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
#define RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
#include <xrpld/ledger/OpenView.h>
#include <xrpld/ledger/detail/ApplyViewBase.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
namespace ripple { namespace ripple {
ApplyViewImpl::ApplyViewImpl(ReadView const* base, ApplyFlags flags) /** Editable, discardable view that can build metadata for one tx.
: ApplyViewBase(base, flags)
{
}
std::optional<TxMeta> Iteration of the tx map is delegated to the base.
ApplyViewImpl::apply(
OpenView& to,
STTx const& tx,
TER ter,
std::optional<uint256> parentBatchId,
bool isDryRun,
beast::Journal j)
{
return items_.apply(to, tx, ter, deliver_, parentBatchId, isDryRun, j);
}
std::size_t @note Presented as ApplyView to clients.
ApplyViewImpl::size() */
class ApplyViewImpl final : public detail::ApplyViewBase
{ {
return items_.size(); public:
} ApplyViewImpl() = delete;
ApplyViewImpl(ApplyViewImpl const&) = delete;
ApplyViewImpl&
operator=(ApplyViewImpl&&) = delete;
ApplyViewImpl&
operator=(ApplyViewImpl const&) = delete;
void ApplyViewImpl(ApplyViewImpl&&) = default;
ApplyViewImpl::visit( ApplyViewImpl(ReadView const* base, ApplyFlags flags);
OpenView& to,
std::function<void( /** Apply the transaction.
uint256 const& key,
bool isDelete, After a call to `apply`, the only valid
std::shared_ptr<SLE const> const& before, operation on this object is to call the
std::shared_ptr<SLE const> const& after)> const& func) destructor.
{ */
items_.visit(to, func); std::optional<TxMeta>
} apply(
OpenView& to,
STTx const& tx,
TER ter,
std::optional<uint256> parentBatchId,
bool isDryRun,
beast::Journal j);
/** Set the amount of currency delivered.
This value is used when generating metadata
for payments, to set the DeliveredAmount field.
If the amount is not specified, the field is
excluded from the resulting metadata.
*/
void
deliver(STAmount const& amount)
{
deliver_ = amount;
}
void
setGasUsed(std::optional<std::uint32_t> const gasUsed)
{
gasUsed_ = gasUsed;
}
void
setWasmReturnCode(std::int32_t const wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
/** Get the number of modified entries
*/
std::size_t
size();
/** Visit modified entries
*/
void
visit(
OpenView& target,
std::function<void(
uint256 const& key,
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)> const& func);
private:
std::optional<STAmount> deliver_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> wasmReturnCode_;
};
} // namespace ripple } // namespace ripple
#endif

View File

@@ -128,6 +128,7 @@ transResults()
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."), MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."), MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
MAKE_ERROR(tecNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."), MAKE_ERROR(tecNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
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."),
@@ -151,6 +152,8 @@ transResults()
MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."), MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."),
MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."), MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."),
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."), MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
MAKE_ERROR(tefNO_WASM, "There is no WASM code to run, but a WASM-specific field was included."),
MAKE_ERROR(tefWASM_FIELD_NOT_INCLUDED, "WASM code requires a field to be included that was not included."),
MAKE_ERROR(telLOCAL_ERROR, "Local failure."), MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."), MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),

View File

@@ -56,9 +56,12 @@ TxMeta::TxMeta(
if (obj.isFieldPresent(sfDeliveredAmount)) if (obj.isFieldPresent(sfDeliveredAmount))
setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount)); setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount));
if (obj.isFieldPresent(sfParentBatchID)) if (obj.isFieldPresent(sfParentBatchID))
setParentBatchId(obj.getFieldH256(sfParentBatchID)); setParentBatchId(obj.getFieldH256(sfParentBatchID));
if (obj.isFieldPresent(sfGasUsed))
setGasUsed(obj.getFieldU32(sfGasUsed));
if (obj.isFieldPresent(sfWasmReturnCode))
setWasmReturnCode(obj.getFieldI32(sfWasmReturnCode));
} }
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj) TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
@@ -82,6 +85,12 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
if (obj.isFieldPresent(sfParentBatchID)) if (obj.isFieldPresent(sfParentBatchID))
setParentBatchId(obj.getFieldH256(sfParentBatchID)); setParentBatchId(obj.getFieldH256(sfParentBatchID));
if (obj.isFieldPresent(sfGasUsed))
setGasUsed(obj.getFieldU32(sfGasUsed));
if (obj.isFieldPresent(sfWasmReturnCode))
setWasmReturnCode(obj.getFieldI32(sfWasmReturnCode));
} }
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, Blob const& vec) TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, Blob const& vec)
@@ -97,15 +106,11 @@ TxMeta::TxMeta(
{ {
} }
TxMeta::TxMeta( TxMeta::TxMeta(uint256 const& transactionID, std::uint32_t ledger)
uint256 const& transactionID,
std::uint32_t ledger,
std::optional<uint256> parentBatchId)
: mTransactionID(transactionID) : mTransactionID(transactionID)
, mLedger(ledger) , mLedger(ledger)
, mIndex(static_cast<std::uint32_t>(-1)) , mIndex(static_cast<std::uint32_t>(-1))
, mResult(255) , mResult(255)
, mParentBatchId(parentBatchId)
, mNodes(sfAffectedNodes) , mNodes(sfAffectedNodes)
{ {
mNodes.reserve(32); mNodes.reserve(32);
@@ -253,9 +258,12 @@ TxMeta::getAsObject() const
metaData.emplace_back(mNodes); metaData.emplace_back(mNodes);
if (hasDeliveredAmount()) if (hasDeliveredAmount())
metaData.setFieldAmount(sfDeliveredAmount, getDeliveredAmount()); metaData.setFieldAmount(sfDeliveredAmount, getDeliveredAmount());
if (hasParentBatchId()) if (hasParentBatchId())
metaData.setFieldH256(sfParentBatchID, getParentBatchId()); metaData.setFieldH256(sfParentBatchID, getParentBatchId());
if (hasGasUsed())
metaData.setFieldU32(sfGasUsed, getGasUsed());
if (hasWasmReturnCode())
metaData.setFieldI32(sfWasmReturnCode, getWasmReturnCode());
return metaData; return metaData;
} }

View File

@@ -17,11 +17,13 @@
*/ */
//============================================================================== //==============================================================================
#include <test/app/wasm_fixtures/fixtures.h>
#include <test/jtx.h> #include <test/jtx.h>
#include <xrpld/app/tx/applySteps.h> #include <xrpld/app/tx/applySteps.h>
#include <xrpld/app/wasm/WasmVM.h>
#include <xrpld/ledger/Dir.h>
#include <xrpl/ledger/Dir.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h> #include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/TxFlags.h> #include <xrpl/protocol/TxFlags.h>
@@ -253,14 +255,6 @@ struct Escrow_test : public beast::unit_test::suite
BEAST_EXPECT(sle); BEAST_EXPECT(sle);
BEAST_EXPECT((*sle)[sfSourceTag] == 1); BEAST_EXPECT((*sle)[sfSourceTag] == 1);
BEAST_EXPECT((*sle)[sfDestinationTag] == 2); BEAST_EXPECT((*sle)[sfDestinationTag] == 2);
if (features[fixIncludeKeyletFields])
{
BEAST_EXPECT((*sle)[sfSequence] == seq);
}
else
{
BEAST_EXPECT(!sle->isFieldPresent(sfSequence));
}
} }
void void
@@ -302,7 +296,7 @@ struct Escrow_test : public beast::unit_test::suite
{ {
testcase("Implied Finish Time (without fix1571)"); testcase("Implied Finish Time (without fix1571)");
Env env(*this, testable_amendments() - fix1571); Env env(*this, features - fix1571);
auto const baseFee = env.current()->fees().base; auto const baseFee = env.current()->fees().base;
env.fund(XRP(5000), "alice", "bob", "carol"); env.fund(XRP(5000), "alice", "bob", "carol");
env.close(); env.close();
@@ -1559,7 +1553,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"};
char const credType[] = "abcde"; char const credType[] = "abcde";
@@ -1701,6 +1695,763 @@ struct Escrow_test : public beast::unit_test::suite
} }
} }
void
testCreateFinishFunctionPreflight(FeatureBitset features)
{
testcase("Test preflight checks involving FinishFunction");
using namespace jtx;
using namespace std::chrono;
Account const alice{"alice"};
Account const carol{"carol"};
// Tests whether the ledger index is >= 5
// getLedgerSqn() >= 5}
static auto wasmHex = ledgerSqnWasmHex;
{
// featureSmartEscrow disabled
Env env(*this, features - featureSmartEscrow);
env.fund(XRP(5000), alice, carol);
XRPAmount const txnFees = env.current()->fees().base + 1000;
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 100s),
fee(txnFees),
ter(temDISABLED));
env.close();
}
{
// FinishFunction > max length
Env env(
*this,
envconfig([](std::unique_ptr<Config> cfg) {
cfg->FEES.extension_size_limit = 10; // 10 bytes
return cfg;
}),
features);
XRPAmount const txnFees = env.current()->fees().base + 1000;
// create escrow
env.fund(XRP(5000), alice, carol);
auto escrowCreate = escrow::create(alice, carol, XRP(500));
// 11-byte string
std::string longWasmHex = "00112233445566778899AA";
env(escrowCreate,
escrow::finish_function(longWasmHex),
escrow::cancel_time(env.now() + 100s),
fee(txnFees),
ter(temMALFORMED));
env.close();
}
Env env(
*this,
envconfig([](std::unique_ptr<Config> cfg) {
cfg->START_UP = Config::FRESH;
return cfg;
}),
features);
XRPAmount const txnFees =
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
// create escrow
env.fund(XRP(5000), alice, carol);
auto escrowCreate = escrow::create(alice, carol, XRP(500));
// Success situations
{
// FinishFunction + CancelAfter
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 20s),
fee(txnFees));
env.close();
}
{
// FinishFunction + Condition + CancelAfter
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 30s),
escrow::condition(escrow::cb1),
fee(txnFees));
env.close();
}
{
// FinishFunction + FinishAfter + CancelAfter
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 40s),
escrow::finish_time(env.now() + 2s),
fee(txnFees));
env.close();
}
{
// FinishFunction + FinishAfter + Condition + CancelAfter
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 50s),
escrow::condition(escrow::cb1),
escrow::finish_time(env.now() + 2s),
fee(txnFees));
env.close();
}
// Failure situations (i.e. all other combinations)
{
// only FinishFunction
env(escrowCreate,
escrow::finish_function(wasmHex),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + FinishAfter
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::finish_time(env.now() + 2s),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + Condition
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::condition(escrow::cb1),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + FinishAfter + Condition
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::condition(escrow::cb1),
escrow::finish_time(env.now() + 2s),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction 0 length
env(escrowCreate,
escrow::finish_function(""),
escrow::cancel_time(env.now() + 60s),
fee(txnFees),
ter(temMALFORMED));
env.close();
}
{
// Not enough fees
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 70s),
fee(txnFees - 1),
ter(telINSUF_FEE_P));
env.close();
}
{
// FinishFunction nonexistent host function
// pub fn finish() -> bool {
// unsafe { host_lib::bad() >= 5 }
// }
auto const badWasmHex =
"0061736d010000000105016000017f02100108686f73745f6c696203626164"
"00000302010005030100100611027f00418080c0000b7f00418080c0000b07"
"2e04066d656d6f727902000666696e69736800010a5f5f646174615f656e64"
"03000b5f5f686561705f6261736503010a09010700100041044a0b004d0970"
"726f64756365727302086c616e6775616765010452757374000c70726f6365"
"737365642d6279010572757374631d312e38352e3120283465623136313235"
"3020323032352d30332d31352900490f7461726765745f6665617475726573"
"042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f72"
"65666572656e63652d74797065732b0a6d756c746976616c7565";
env(escrowCreate,
escrow::finish_function(badWasmHex),
escrow::cancel_time(env.now() + 100s),
fee(txnFees),
ter(temBAD_WASM));
env.close();
}
}
void
testFinishWasmFailures(FeatureBitset features)
{
testcase("EscrowFinish Smart Escrow failures");
using namespace jtx;
using namespace std::chrono;
Account const alice{"alice"};
Account const carol{"carol"};
// Tests whether the ledger index is >= 5
// getLedgerSqn() >= 5}
static auto wasmHex = ledgerSqnWasmHex;
{
// featureSmartEscrow disabled
Env env(*this, features - featureSmartEscrow);
env.fund(XRP(5000), alice, carol);
XRPAmount const txnFees =
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
env(escrow::finish(carol, alice, 1),
fee(txnFees),
escrow::comp_allowance(4),
ter(temDISABLED));
env.close();
}
{
// ComputationAllowance > max compute limit
Env env(
*this,
envconfig([](std::unique_ptr<Config> cfg) {
cfg->FEES.extension_compute_limit = 1'000; // in gas
return cfg;
}),
features);
env.fund(XRP(5000), alice, carol);
// Run past the flag ledger so that a Fee change vote occurs and
// updates FeeSettings. (It also activates all supported
// amendments.)
for (auto i = env.current()->seq(); i <= 257; ++i)
env.close();
auto const allowance = 1'001;
env(escrow::finish(carol, alice, 1),
fee(env.current()->fees().base + allowance),
escrow::comp_allowance(allowance),
ter(temBAD_LIMIT));
}
Env env(*this, features);
// Run past the flag ledger so that a Fee change vote occurs and
// updates FeeSettings. (It also activates all supported
// amendments.)
for (auto i = env.current()->seq(); i <= 257; ++i)
env.close();
XRPAmount const txnFees =
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
env.fund(XRP(5000), alice, carol);
// create escrow
auto const seq = env.seq(alice);
env(escrow::create(alice, carol, XRP(500)),
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 100s),
fee(txnFees));
env.close();
{
// no ComputationAllowance field
env(escrow::finish(carol, alice, seq),
ter(tefWASM_FIELD_NOT_INCLUDED));
}
{
// ComputationAllowance value of 0
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(0),
ter(temBAD_LIMIT));
}
{
// not enough fees
// This function takes 4 gas
// In testing, 1 gas costs 1 drop
auto const finishFee = env.current()->fees().base + 3;
env(escrow::finish(carol, alice, seq),
fee(finishFee),
escrow::comp_allowance(4),
ter(telINSUF_FEE_P));
}
{
// not enough gas
// This function takes 4 gas
// In testing, 1 gas costs 1 drop
auto const finishFee = env.current()->fees().base + 4;
env(escrow::finish(carol, alice, seq),
fee(finishFee),
escrow::comp_allowance(2),
ter(tecFAILED_PROCESSING));
}
{
// ComputationAllowance field included w/no FinishFunction on
// escrow
auto const seq2 = env.seq(alice);
env(escrow::create(alice, carol, XRP(500)),
escrow::finish_time(env.now() + 10s),
escrow::cancel_time(env.now() + 100s));
env.close();
auto const allowance = 100;
env(escrow::finish(carol, alice, seq2),
fee(env.current()->fees().base +
(allowance * env.current()->fees().gasPrice) /
MICRO_DROPS_PER_DROP +
1),
escrow::comp_allowance(allowance),
ter(tefNO_WASM));
}
}
void
testFinishFunction(FeatureBitset features)
{
testcase("Example escrow function");
using namespace jtx;
using namespace std::chrono;
Account const alice{"alice"};
Account const carol{"carol"};
// Tests whether the ledger index is >= 5
// getLedgerSqn() >= 5}
auto const& wasmHex = ledgerSqnWasmHex;
std::uint32_t const allowance = 66;
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
auto [createFee, finishFee] = [&]() {
Env env(*this, features);
auto createFee =
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
auto finishFee = env.current()->fees().base +
(allowance * env.current()->fees().gasPrice) /
MICRO_DROPS_PER_DROP +
1;
return std::make_pair(createFee, finishFee);
}();
{
// basic FinishFunction situation
Env env(*this, features);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT(env.ownerCount(alice) == 0);
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 100s),
fee(createFee));
env.close();
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
{
env.require(balance(alice, XRP(4000) - createFee));
env.require(balance(carol, XRP(5000)));
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee),
ter(tecWASM_REJECTED));
env(escrow::finish(alice, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee),
ter(tecWASM_REJECTED));
env(escrow::finish(alice, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee),
ter(tecWASM_REJECTED));
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee),
ter(tecWASM_REJECTED));
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee),
ter(tecWASM_REJECTED));
env.close();
{
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECTS(
env.meta()->getFieldU32(sfGasUsed) == allowance,
std::to_string(env.meta()->getFieldU32(sfGasUsed)));
}
env(escrow::finish(alice, alice, seq),
fee(finishFee),
escrow::comp_allowance(allowance),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECTS(
txMeta->getFieldU32(sfGasUsed) == allowance,
std::to_string(txMeta->getFieldU32(sfGasUsed)));
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECTS(
txMeta->getFieldI32(sfWasmReturnCode) == 5,
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
BEAST_EXPECT(env.ownerCount(alice) == 0);
}
}
{
// FinishFunction + Condition
Env env(*this, features);
env.fund(XRP(5000), alice, carol);
BEAST_EXPECT(env.ownerCount(alice) == 0);
auto const seq = env.seq(alice);
// create escrow
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::condition(escrow::cb1),
escrow::cancel_time(env.now() + 100s),
fee(createFee));
env.close();
auto const conditionFinishFee = finishFee +
env.current()->fees().base * (32 + (escrow::fb1.size() / 16));
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
{
env.require(balance(alice, XRP(4000) - createFee));
env.require(balance(carol, XRP(5000)));
// no fulfillment provided, function fails
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee),
ter(tecCRYPTOCONDITION_ERROR));
// fulfillment provided, function fails
env(escrow::finish(carol, alice, seq),
escrow::condition(escrow::cb1),
escrow::fulfillment(escrow::fb1),
escrow::comp_allowance(allowance),
fee(conditionFinishFee),
ter(tecWASM_REJECTED));
if (BEAST_EXPECT(env.meta()->isFieldPresent(sfGasUsed)))
BEAST_EXPECTS(
env.meta()->getFieldU32(sfGasUsed) == allowance,
std::to_string(env.meta()->getFieldU32(sfGasUsed)));
env.close();
// no fulfillment provided, function succeeds
env(escrow::finish(alice, alice, seq),
escrow::comp_allowance(allowance),
fee(conditionFinishFee),
ter(tecCRYPTOCONDITION_ERROR));
// wrong fulfillment provided, function succeeds
env(escrow::finish(alice, alice, seq),
escrow::condition(escrow::cb1),
escrow::fulfillment(escrow::fb2),
escrow::comp_allowance(allowance),
fee(conditionFinishFee),
ter(tecCRYPTOCONDITION_ERROR));
// fulfillment provided, function succeeds, tx succeeds
env(escrow::finish(alice, alice, seq),
escrow::condition(escrow::cb1),
escrow::fulfillment(escrow::fb1),
escrow::comp_allowance(allowance),
fee(conditionFinishFee),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECTS(
txMeta->getFieldI32(sfWasmReturnCode) == 6,
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
env.close();
BEAST_EXPECT(env.ownerCount(alice) == 0);
}
}
{
// FinishFunction + FinishAfter
Env env(*this, features);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT(env.ownerCount(alice) == 0);
auto const ts = env.now() + 97s;
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::finish_time(ts),
escrow::cancel_time(env.now() + 1000s),
fee(createFee));
env.close();
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
{
env.require(balance(alice, XRP(4000) - createFee));
env.require(balance(carol, XRP(5000)));
// finish time hasn't passed, function fails
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee + 1),
ter(tecNO_PERMISSION));
env.close();
// finish time hasn't passed, function succeeds
for (; env.now() < ts; env.close())
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee + 2),
ter(tecNO_PERMISSION));
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee + 1),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECTS(
txMeta->getFieldI32(sfWasmReturnCode) == 13,
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
BEAST_EXPECT(env.ownerCount(alice) == 0);
}
}
{
// FinishFunction + FinishAfter #2
Env env(*this, features);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT(env.ownerCount(alice) == 0);
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::finish_time(env.now() + 2s),
escrow::cancel_time(env.now() + 100s),
fee(createFee));
// Don't close the ledger here
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
{
env.require(balance(alice, XRP(4000) - createFee));
env.require(balance(carol, XRP(5000)));
// finish time hasn't passed, function fails
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee),
ter(tecNO_PERMISSION));
env.close();
// finish time has passed, function fails
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee),
ter(tecWASM_REJECTED));
if (BEAST_EXPECT(env.meta()->isFieldPresent(sfGasUsed)))
BEAST_EXPECTS(
env.meta()->getFieldU32(sfGasUsed) == allowance,
std::to_string(env.meta()->getFieldU32(sfGasUsed)));
env.close();
// finish time has passed, function succeeds, tx succeeds
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECTS(
txMeta->getFieldI32(sfWasmReturnCode) == 6,
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
env.close();
BEAST_EXPECT(env.ownerCount(alice) == 0);
}
}
}
void
testAllHostFunctions(FeatureBitset features)
{
testcase("Test all host functions");
using namespace jtx;
using namespace std::chrono;
// TODO: create wasm module for all host functions
static auto wasmHex = allHostFunctionsWasmHex;
Account const alice{"alice"};
Account const carol{"carol"};
{
Env env(*this, features);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT(env.ownerCount(alice) == 0);
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
XRPAmount txnFees =
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::finish_time(env.now() + 11s),
escrow::cancel_time(env.now() + 100s),
escrow::data("1000000000"), // 1000 XRP in drops
fee(txnFees));
env.close();
if (BEAST_EXPECT(
env.ownerCount(alice) == (1 + wasmHex.size() / 2 / 500)))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
auto const allowance = 1'000'000;
XRPAmount const finishFee = env.current()->fees().base +
(allowance * env.current()->fees().gasPrice) /
MICRO_DROPS_PER_DROP +
1;
// FinishAfter time hasn't passed
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee),
ter(tecNO_PERMISSION));
env.close();
env.close();
env.close();
// reduce the destination balance
env(pay(carol, alice, XRP(4500)));
env.close();
env.close();
env(escrow::finish(alice, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECTS(
txMeta->getFieldU32(sfGasUsed) == 38'571,
std::to_string(txMeta->getFieldU32(sfGasUsed)));
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECT(txMeta->getFieldI32(sfWasmReturnCode) == 1);
env.close();
BEAST_EXPECT(env.ownerCount(alice) == 0);
}
}
}
void
testKeyletHostFunctions(FeatureBitset features)
{
testcase("Test all keylet host functions");
using namespace jtx;
using namespace std::chrono;
// TODO: create wasm module for all host functions
static auto wasmHex = allKeyletsWasmHex;
Account const alice{"alice"};
Account const carol{"carol"};
{
Env env{*this};
env.fund(XRP(10000), alice, carol);
BEAST_EXPECT(env.seq(alice) == 4);
BEAST_EXPECT(env.ownerCount(alice) == 0);
// base objects that need to be created first
auto const tokenId =
token::getNextID(env, alice, 0, tfTransferable);
env(token::mint(alice, 0u), txflags(tfTransferable));
env(trust(alice, carol["USD"](1'000'000)));
env.close();
BEAST_EXPECT(env.seq(alice) == 6);
BEAST_EXPECT(env.ownerCount(alice) == 2);
// set up a bunch of objects to check their keylets
AMM amm(env, carol, XRP(10), carol["USD"](1000));
env(check::create(alice, carol, XRP(100)));
env(credentials::create(alice, alice, "termsandconditions"));
env(delegate::set(alice, carol, {"TrustSet"}));
env(deposit::auth(alice, carol));
env(did::set(alice), did::data("alice_did"));
env(escrow::create(alice, carol, XRP(100)),
escrow::finish_time(env.now() + 100s));
MPTTester mptTester{env, alice, {.fund = false}};
mptTester.create();
mptTester.authorize({.account = carol});
env(token::createOffer(carol, tokenId, XRP(100)),
token::owner(alice));
env(offer(alice, carol["GBP"](0.1), XRP(100)));
env(create(alice, carol, XRP(1000), 100s, alice.pk()));
pdomain::Credentials credentials{{alice, "first credential"}};
env(pdomain::setTx(alice, credentials));
env(signers(alice, 1, {{carol, 1}}));
env(ticket::create(alice, 1));
Vault vault{env};
auto [tx, _keylet] =
vault.create({.owner = alice, .asset = xrpIssue()});
env(tx);
env.close();
BEAST_EXPECTS(
env.ownerCount(alice) == 16,
std::to_string(env.ownerCount(alice)));
if (BEAST_EXPECTS(
env.seq(alice) == 20, std::to_string(env.seq(alice))))
{
auto const seq = env.seq(alice);
XRPAmount txnFees =
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
env(escrow::create(alice, carol, XRP(1000)),
escrow::finish_function(wasmHex),
escrow::finish_time(env.now() + 2s),
escrow::cancel_time(env.now() + 100s),
fee(txnFees));
env.close();
env.close();
env.close();
auto const allowance = 137'596;
auto const finishFee = env.current()->fees().base +
(allowance * env.current()->fees().gasPrice) /
MICRO_DROPS_PER_DROP +
1;
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(finishFee));
env.close();
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed)))
{
auto const gasUsed = txMeta->getFieldU32(sfGasUsed);
BEAST_EXPECTS(
gasUsed == allowance, std::to_string(gasUsed));
}
BEAST_EXPECTS(
env.ownerCount(alice) == 16,
std::to_string(env.ownerCount(alice)));
}
}
}
void void
testWithFeats(FeatureBitset features) testWithFeats(FeatureBitset features)
{ {
@@ -1716,6 +2467,13 @@ struct Escrow_test : public beast::unit_test::suite
testConsequences(features); testConsequences(features);
testEscrowWithTickets(features); testEscrowWithTickets(features);
testCredentials(features); testCredentials(features);
testCreateFinishFunctionPreflight(features);
testFinishWasmFailures(features);
testFinishFunction(features);
// TODO: Update module with new host functions
testAllHostFunctions(features);
testKeyletHostFunctions(features);
} }
public: public:
@@ -1726,8 +2484,7 @@ public:
FeatureBitset const all{testable_amendments()}; FeatureBitset const all{testable_amendments()};
testWithFeats(all); testWithFeats(all);
testWithFeats(all - featureTokenEscrow); testWithFeats(all - featureTokenEscrow);
testTags(all - fixIncludeKeyletFields); };
}
}; };
BEAST_DEFINE_TESTSUITE(Escrow, app, ripple); BEAST_DEFINE_TESTSUITE(Escrow, app, ripple);

View File

@@ -24,8 +24,7 @@
#include <xrpld/app/tx/detail/NFTokenUtils.h> #include <xrpld/app/tx/detail/NFTokenUtils.h>
#include <xrpld/app/wasm/HostFunc.h> #include <xrpld/app/wasm/HostFunc.h>
#include <xrpld/app/wasm/WasmVM.h> #include <xrpld/app/wasm/WasmVM.h>
#include <xrpld/ledger/detail/ApplyViewBase.h>
#include <xrpl/ledger/detail/ApplyViewBase.h>
namespace ripple { namespace ripple {

View File

@@ -649,7 +649,7 @@ struct Wasm_test : public beast::unit_test::suite
Bytes const wasm(wasmStr.begin(), wasmStr.end()); Bytes const wasm(wasmStr.begin(), wasmStr.end());
TestHostFunctions hfs(env, 0); TestHostFunctions hfs(env, 0);
auto const allowance = 148'406; auto const allowance = 121'895;
auto re = runEscrowWasm( auto re = runEscrowWasm(
wasm, ESCROW_FUNCTION_NAME, {}, &hfs, allowance, env.journal); wasm, ESCROW_FUNCTION_NAME, {}, &hfs, allowance, env.journal);
@@ -709,7 +709,7 @@ struct Wasm_test : public beast::unit_test::suite
testWasmSha(); testWasmSha();
testWasmB58(); testWasmB58();
// running too long // runing too long
// testWasmSP1Verifier(); // testWasmSP1Verifier();
testWasmBG16Verifier(); testWasmBG16Verifier();

View File

@@ -22,7 +22,6 @@
#include <test/jtx/Account.h> #include <test/jtx/Account.h>
#include <test/jtx/Env.h> #include <test/jtx/Env.h>
#include <test/jtx/TestHelpers.h>
#include <test/jtx/owners.h> #include <test/jtx/owners.h>
#include <test/jtx/rate.h> #include <test/jtx/rate.h>
@@ -95,14 +94,156 @@ std::array<std::uint8_t, 39> const cb3 = {
0x57, 0x0D, 0x15, 0x85, 0x8B, 0xD4, 0x81, 0x01, 0x04}}; 0x57, 0x0D, 0x15, 0x85, 0x8B, 0xD4, 0x81, 0x01, 0x04}};
/** Set the "FinishAfter" time tag on a JTx */ /** Set the "FinishAfter" time tag on a JTx */
auto const finish_time = JTxFieldWrapper<timePointField>(sfFinishAfter); struct finish_time
{
private:
NetClock::time_point value_;
public:
explicit finish_time(NetClock::time_point const& value) : value_(value)
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfFinishAfter.jsonName] = value_.time_since_epoch().count();
}
};
/** Set the "CancelAfter" time tag on a JTx */ /** Set the "CancelAfter" time tag on a JTx */
auto const cancel_time = JTxFieldWrapper<timePointField>(sfCancelAfter); struct cancel_time
{
private:
NetClock::time_point value_;
auto const condition = JTxFieldWrapper<blobField>(sfCondition); public:
explicit cancel_time(NetClock::time_point const& value) : value_(value)
{
}
auto const fulfillment = JTxFieldWrapper<blobField>(sfFulfillment); void
operator()(jtx::Env&, jtx::JTx& jt) const
{
jt.jv[sfCancelAfter.jsonName] = value_.time_since_epoch().count();
}
};
struct condition
{
private:
std::string value_;
public:
explicit condition(Slice const& cond) : value_(strHex(cond))
{
}
template <size_t N>
explicit condition(std::array<std::uint8_t, N> const& c)
: condition(makeSlice(c))
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfCondition.jsonName] = value_;
}
};
struct fulfillment
{
private:
std::string value_;
public:
explicit fulfillment(Slice condition) : value_(strHex(condition))
{
}
template <size_t N>
explicit fulfillment(std::array<std::uint8_t, N> f)
: fulfillment(makeSlice(f))
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfFulfillment.jsonName] = value_;
}
};
struct finish_function
{
private:
std::string value_;
public:
explicit finish_function(std::string func) : value_(func)
{
}
explicit finish_function(Slice const& func) : value_(strHex(func))
{
}
template <size_t N>
explicit finish_function(std::array<std::uint8_t, N> const& f)
: finish_function(makeSlice(f))
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfFinishFunction.jsonName] = value_;
}
};
struct data
{
private:
std::string value_;
public:
explicit data(std::string func) : value_(func)
{
}
explicit data(Slice const& func) : value_(strHex(func))
{
}
template <size_t N>
explicit data(std::array<std::uint8_t, N> const& f) : data(makeSlice(f))
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfData.jsonName] = value_;
}
};
struct comp_allowance
{
private:
std::uint32_t value_;
public:
explicit comp_allowance(std::uint32_t const& value) : value_(value)
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfComputationAllowance.jsonName] = value_;
}
};
} // namespace escrow } // namespace escrow

View File

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

View File

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

View File

@@ -22,9 +22,9 @@
#include <xrpld/app/main/Application.h> #include <xrpld/app/main/Application.h>
#include <xrpld/core/Config.h> #include <xrpld/core/Config.h>
#include <xrpld/ledger/ApplyViewImpl.h>
#include <xrpl/beast/utility/Journal.h> #include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyViewImpl.h>
#include <xrpl/protocol/STTx.h> #include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/XRPAmount.h> #include <xrpl/protocol/XRPAmount.h>
@@ -106,6 +106,20 @@ public:
view_->deliver(amount); view_->deliver(amount);
} }
/** Sets the gas used in the metadata */
void
setGasUsed(std::uint32_t const gasUsed)
{
gasUsed_ = gasUsed;
}
/** Sets the gas used in the metadata */
void
setWasmReturnCode(std::int32_t const wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
/** Discard changes and start fresh. */ /** Discard changes and start fresh. */
void void
discard(); discard();
@@ -157,6 +171,8 @@ private:
// The ID of the batch transaction we are executing under, if seated. // The ID of the batch transaction we are executing under, if seated.
std::optional<uint256 const> parentBatchId_; std::optional<uint256 const> parentBatchId_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> wasmReturnCode_;
}; };
} // namespace ripple } // namespace ripple

View File

@@ -17,23 +17,26 @@
*/ */
//============================================================================== //==============================================================================
#include <xrpld/app/misc/CredentialHelpers.h>
#include <xrpld/app/misc/HashRouter.h> #include <xrpld/app/misc/HashRouter.h>
#include <xrpld/app/tx/detail/Escrow.h> #include <xrpld/app/tx/detail/Escrow.h>
#include <xrpld/app/tx/detail/MPTokenAuthorize.h> #include <xrpld/app/tx/detail/MPTokenAuthorize.h>
#include <xrpld/app/wasm/HostFuncImpl.h>
#include <xrpld/app/wasm/WasmVM.h>
#include <xrpld/conditions/Condition.h> #include <xrpld/conditions/Condition.h>
#include <xrpld/conditions/Fulfillment.h> #include <xrpld/conditions/Fulfillment.h>
#include <xrpld/ledger/ApplyView.h>
#include <xrpld/ledger/View.h>
#include <xrpl/basics/Log.h> #include <xrpl/basics/Log.h>
#include <xrpl/basics/chrono.h> #include <xrpl/basics/chrono.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/CredentialHelpers.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h> #include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/MPTAmount.h> #include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/TxFlags.h> #include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/XRPAmount.h> #include <xrpl/protocol/XRPAmount.h>
#include <algorithm>
namespace ripple { namespace ripple {
// During an EscrowFinish, the transaction must specify both // During an EscrowFinish, the transaction must specify both
@@ -81,8 +84,8 @@ constexpr HashRouterFlags SF_CF_VALID = HashRouterFlags::PRIVATE6;
TxConsequences TxConsequences
EscrowCreate::makeTxConsequences(PreflightContext const& ctx) EscrowCreate::makeTxConsequences(PreflightContext const& ctx)
{ {
auto const amount = ctx.tx[sfAmount]; return TxConsequences{
return TxConsequences{ctx.tx, isXRP(amount) ? amount.xrp() : beast::zero}; ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::zero};
} }
template <ValidIssueType T> template <ValidIssueType T>
@@ -118,9 +121,29 @@ escrowCreatePreflightHelper<MPTIssue>(PreflightContext const& ctx)
return tesSUCCESS; return tesSUCCESS;
} }
XRPAmount
EscrowCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
{
XRPAmount txnFees{Transactor::calculateBaseFee(view, tx)};
if (tx.isFieldPresent(sfFinishFunction))
{
// 10 base fees for the transaction (1 is in
// `Transactor::calculateBaseFee`), plus 5 drops per byte
txnFees += 9 * view.fees().base + 5 * tx[sfFinishFunction].size();
}
return txnFees;
}
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;
@@ -157,14 +180,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])
@@ -189,6 +221,27 @@ 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;
}
HostFunctions mock;
auto const re =
preflightEscrowWasm(code, ESCROW_FUNCTION_NAME, {}, &mock, ctx.j);
if (!isTesSuccess(re))
{
JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM";
return re;
}
}
return preflight2(ctx); return preflight2(ctx);
} }
@@ -451,6 +504,17 @@ escrowLockApplyHelper<MPTIssue>(
return tesSUCCESS; return tesSUCCESS;
} }
template <class T>
static uint32_t
calculateAdditionalReserve(T const& finishFunction)
{
if (!finishFunction)
return 1;
// First 500 bytes included in the normal reserve
// Each additional 500 bytes requires an additional reserve
return 1 + (finishFunction->size() / 500);
}
TER TER
EscrowCreate::doApply() EscrowCreate::doApply()
{ {
@@ -494,9 +558,11 @@ EscrowCreate::doApply()
// Check reserve and funds availability // Check reserve and funds availability
STAmount const amount{ctx_.tx[sfAmount]}; STAmount const amount{ctx_.tx[sfAmount]};
auto const reserveToAdd =
calculateAdditionalReserve(ctx_.tx[~sfFinishFunction]);
auto const reserve = auto const reserve =
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1); ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + reserveToAdd);
if (mSourceBalance < reserve) if (mSourceBalance < reserve)
return tecINSUFFICIENT_RESERVE; return tecINSUFFICIENT_RESERVE;
@@ -537,11 +603,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];
if (ctx_.view().rules().enabled(fixIncludeKeyletFields)) (*slep)[~sfData] = ctx_.tx[~sfData];
{
(*slep)[sfSequence] = ctx_.tx.getSeqValue();
}
if (ctx_.view().rules().enabled(featureTokenEscrow) && !isXRP(amount)) if (ctx_.view().rules().enabled(featureTokenEscrow) && !isXRP(amount))
{ {
@@ -604,7 +667,8 @@ EscrowCreate::doApply()
} }
// increment owner count // increment owner count
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); // TODO: determine actual reserve based on FinishFunction size
adjustOwnerCount(ctx_.view(), sle, reserveToAdd, ctx_.journal);
ctx_.view().update(sle); ctx_.view().update(sle);
return tesSUCCESS; return tesSUCCESS;
} }
@@ -639,6 +703,13 @@ EscrowFinish::preflight(PreflightContext const& ctx)
!ctx.rules.enabled(featureCredentials)) !ctx.rules.enabled(featureCredentials))
return temDISABLED; return temDISABLED;
if (ctx.tx.isFieldPresent(sfComputationAllowance) &&
!ctx.rules.enabled(featureSmartEscrow))
{
JLOG(ctx.j.debug()) << "SmartEscrow not enabled";
return temDISABLED;
}
if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret; return ret;
@@ -648,7 +719,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.
@@ -677,6 +751,20 @@ EscrowFinish::preflight(PreflightContext const& ctx)
} }
} }
if (auto const allowance = ctx.tx[~sfComputationAllowance]; allowance)
{
if (*allowance == 0)
{
return temBAD_LIMIT;
}
if (*allowance > ctx.app.config().FEES.extension_compute_limit)
{
JLOG(ctx.j.debug())
<< "ComputationAllowance too large: " << *allowance;
return temBAD_LIMIT;
}
}
if (auto const err = credentials::checkFields(ctx.tx, ctx.j); if (auto const err = credentials::checkFields(ctx.tx, ctx.j);
!isTesSuccess(err)) !isTesSuccess(err))
return err; return err;
@@ -693,7 +781,14 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
{ {
extraFee += view.fees().base * (32 + (fb->size() / 16)); extraFee += view.fees().base * (32 + (fb->size() / 16));
} }
if (auto const allowance = tx[~sfComputationAllowance]; allowance)
{
// The extra fee is the allowance in drops, rounded up to the nearest
// whole drop.
// Integer math rounds down by default, so we add 1 to round up.
extraFee +=
((*allowance) * view.fees().gasPrice) / MICRO_DROPS_PER_DROP + 1;
}
return Transactor::calculateBaseFee(view, tx) + extraFee; return Transactor::calculateBaseFee(view, tx) + extraFee;
} }
@@ -773,25 +868,52 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
return err; 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 k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]); auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
auto const slep = ctx.view.read(k); auto const slep = ctx.view.read(k);
if (!slep) if (!slep)
return tecNO_TARGET; return tecNO_TARGET;
AccountID const dest = (*slep)[sfDestination]; if (ctx.view.rules().enabled(featureSmartEscrow))
STAmount const amount = (*slep)[sfAmount];
if (!isXRP(amount))
{ {
if (auto const ret = std::visit( if (slep->isFieldPresent(sfFinishFunction))
[&]<typename T>(T const&) { {
return escrowFinishPreclaimHelper<T>(ctx, dest, amount); if (!ctx.tx.isFieldPresent(sfComputationAllowance))
}, {
amount.asset().value()); JLOG(ctx.j.debug())
!isTesSuccess(ret)) << "FinishFunction requires ComputationAllowance";
return ret; return tefWASM_FIELD_NOT_INCLUDED;
}
}
else
{
if (ctx.tx.isFieldPresent(sfComputationAllowance))
{
JLOG(ctx.j.debug()) << "FinishFunction not present, "
"ComputationAllowance present";
return tefNO_WASM;
}
}
}
if (ctx.view.rules().enabled(featureTokenEscrow))
{
AccountID const dest = (*slep)[sfDestination];
STAmount const amount = (*slep)[sfAmount];
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; return tesSUCCESS;
@@ -1012,13 +1134,8 @@ escrowUnlockApplyHelper<MPTIssue>(
// compute balance to transfer // compute balance to transfer
finalAmt = amount.value() - xferFee; finalAmt = amount.value() - xferFee;
} }
return rippleUnlockEscrowMPT(
view, return rippleUnlockEscrowMPT(view, sender, receiver, finalAmt, journal);
sender,
receiver,
finalAmt,
view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
journal);
} }
TER TER
@@ -1028,7 +1145,8 @@ EscrowFinish::doApply()
auto const slep = ctx_.view().peek(k); auto const slep = ctx_.view().peek(k);
if (!slep) 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 tecINTERNAL; // LCOV_EXCL_LINE
return tecNO_TARGET; return tecNO_TARGET;
@@ -1043,11 +1161,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
{ {
@@ -1055,13 +1179,36 @@ 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_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
!isTesSuccess(err))
return err;
}
} }
// Check cryptocondition fulfillment // Check cryptocondition fulfillment
@@ -1111,18 +1258,65 @@ 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( // NOTE: Escrow payments cannot be used to fund accounts.
ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal); if (!sled)
!isTesSuccess(err)) return tecNO_DST;
return err;
if (ctx_.view().rules().enabled(featureDepositAuth))
{
if (auto err = verifyDepositPreauth(
ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
!isTesSuccess(err))
return err;
}
}
// Execute custom release function
if ((*slep)[~sfFinishFunction])
{
JLOG(j_.trace())
<< "The escrow has a finish function, running WASM code...";
// WASM execution
auto const wasmStr = slep->getFieldVL(sfFinishFunction);
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
WasmHostFunctionsImpl ledgerDataProvider(ctx_, k);
if (!ctx_.tx.isFieldPresent(sfComputationAllowance))
{
// already checked above, this check is just in case
return tecINTERNAL;
}
std::uint32_t allowance = ctx_.tx[sfComputationAllowance];
auto re = runEscrowWasm(
wasm, ESCROW_FUNCTION_NAME, {}, &ledgerDataProvider, allowance);
JLOG(j_.trace()) << "Escrow WASM ran";
if (auto const& data = ledgerDataProvider.getData(); data.has_value())
{
slep->setFieldVL(sfData, makeSlice(*data));
}
if (re.has_value())
{
auto reValue = re.value().result;
ctx_.setWasmReturnCode(reValue);
// TODO: better error handling for this conversion
ctx_.setGasUsed(static_cast<uint32_t>(re.value().cost));
JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue)
<< ", cost: " << re.value().cost;
if (reValue <= 0)
{
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];
@@ -1195,9 +1389,12 @@ EscrowFinish::doApply()
ctx_.view().update(sled); ctx_.view().update(sled);
auto const reserveToSubtract =
calculateAdditionalReserve((*slep)[~sfFinishFunction]);
// 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, -1 * reserveToSubtract, ctx_.journal);
ctx_.view().update(sle); ctx_.view().update(sle);
// Remove escrow from ledger // Remove escrow from ledger
@@ -1408,7 +1605,9 @@ EscrowCancel::doApply()
} }
} }
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); auto const reserveToSubtract =
calculateAdditionalReserve((*slep)[~sfFinishFunction]);
adjustOwnerCount(ctx_.view(), sle, -1 * reserveToSubtract, 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

@@ -18,6 +18,7 @@
//============================================================================== //==============================================================================
#include <xrpld/app/main/Application.h> #include <xrpld/app/main/Application.h>
#include <xrpld/app/misc/CredentialHelpers.h>
#include <xrpld/app/misc/DelegateUtils.h> #include <xrpld/app/misc/DelegateUtils.h>
#include <xrpld/app/misc/LoadFeeTrack.h> #include <xrpld/app/misc/LoadFeeTrack.h>
#include <xrpld/app/tx/apply.h> #include <xrpld/app/tx/apply.h>
@@ -25,12 +26,11 @@
#include <xrpld/app/tx/detail/SignerEntries.h> #include <xrpld/app/tx/detail/SignerEntries.h>
#include <xrpld/app/tx/detail/Transactor.h> #include <xrpld/app/tx/detail/Transactor.h>
#include <xrpld/core/Config.h> #include <xrpld/core/Config.h>
#include <xrpld/ledger/View.h>
#include <xrpl/basics/Log.h> #include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h> #include <xrpl/basics/contract.h>
#include <xrpl/json/to_string.h> #include <xrpl/json/to_string.h>
#include <xrpl/ledger/CredentialHelpers.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h> #include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h> #include <xrpl/protocol/Protocol.h>
@@ -206,10 +206,7 @@ preflight2(PreflightContext const& ctx)
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
Transactor::Transactor(ApplyContext& ctx) Transactor::Transactor(ApplyContext& ctx)
: ctx_(ctx) : ctx_(ctx), j_(ctx.journal), account_(ctx.tx.getAccountID(sfAccount))
, sink_(ctx.journal, to_short_string(ctx.tx.getTransactionID()) + " ")
, j_(sink_)
, account_(ctx.tx.getAccountID(sfAccount))
{ {
} }
@@ -967,6 +964,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,
@@ -1124,6 +1137,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);
@@ -1136,13 +1150,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,
@@ -1151,7 +1168,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,
@@ -1186,6 +1205,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)));
}
}); });
} }
@@ -1215,6 +1241,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);
} }