From 028783661d3fc0bbfeafcc3b1aa60187af6a0ed1 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 9 Sep 2026 20:05:58 +0000 Subject: [PATCH] feat: Apply .macro changes from ripple/smart-escrow (#8157) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- include/xrpl/protocol/TER.h | 7 ++ include/xrpl/protocol/detail/features.macro | 1 + .../xrpl/protocol/detail/ledger_entries.macro | 7 ++ include/xrpl/protocol/detail/sfields.macro | 7 ++ .../xrpl/protocol/detail/transactions.macro | 9 +- .../protocol_autogen/ledger_entries/Escrow.h | 70 +++++++++++ .../ledger_entries/FeeSettings.h | 105 +++++++++++++++++ .../transactions/EscrowCreate.h | 96 +++++++++++++-- .../transactions/EscrowFinish.h | 37 ++++++ .../protocol_autogen/transactions/SetFee.h | 111 ++++++++++++++++++ src/libxrpl/protocol/STValidation.cpp | 4 + src/libxrpl/protocol/TER.cpp | 6 + .../tx/transactors/escrow/EscrowCreate.cpp | 9 +- .../tx/transactors/escrow/EscrowFinish.cpp | 8 +- src/libxrpl/tx/transactors/system/Change.cpp | 6 + .../ledger_entries/EscrowTests.cpp | 54 +++++++++ .../ledger_entries/FeeSettingsTests.cpp | 81 +++++++++++++ .../transactions/EscrowCreateTests.cpp | 72 +++++++++--- .../transactions/EscrowFinishTests.cpp | 21 ++++ .../transactions/SetFeeTests.cpp | 63 ++++++++++ 20 files changed, 744 insertions(+), 30 deletions(-) diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index 730d021254..5702b01d1d 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -129,8 +129,11 @@ enum TEMcodes : TERUnderlyingType { temARRAY_TOO_LARGE, temBAD_TRANSFER_FEE, temINVALID_INNER_BATCH, + temBAD_MPT, temBAD_CIPHERTEXT, + temINVALID_BYTECODE, + temTEMP_DISABLED, }; //------------------------------------------------------------------------------ @@ -179,6 +182,8 @@ enum TEFcodes : TERUnderlyingType { tefINVALID_LEDGER_FIX_TYPE, tefNO_DST_PARTIAL, tefBAD_PATH_COUNT, + tefNO_BYTECODE, + tefBYTECODE_NOT_INCLUDED, }; //------------------------------------------------------------------------------ @@ -370,6 +375,8 @@ enum TECcodes : TERUnderlyingType { tecNO_DELEGATE_PERMISSION = 198, tecBAD_PROOF = 199, tecNO_SPONSOR_PERMISSION = 200, + tecOUT_OF_GAS = 201, + tecBYTECODE_REJECTED = 202, }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 4cece9fc23..e63a7f515d 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -15,6 +15,7 @@ // Add new amendments to the top of this list. // Keep it sorted in reverse chronological order. +XRPL_FEATURE(SmartEscrow, Supported::No, VoteBehavior::DefaultNo) XRPL_FEATURE(LendingProtocolV1_2, Supported::No, VoteBehavior::DefaultNo) XRPL_FIX (Cleanup3_5_0, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FEATURE(ConfidentialMPTKeyRotation, Supported::No, VoteBehavior::DefaultNo) diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index be12ad6348..18c71b572c 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -309,6 +309,11 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({ {sfBaseFeeDrops, SoeOptional}, {sfReserveBaseDrops, SoeOptional}, {sfReserveIncrementDrops, SoeOptional}, + // Smart Escrow fields + {sfGasLimit, SoeOptional}, + {sfBytecodeSizeLimit, SoeOptional}, + {sfGasPrice, SoeOptional}, + {sfPreviousTxnID, SoeOptional}, {sfPreviousTxnLgrSeq, SoeOptional}, })) @@ -339,6 +344,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({ {sfCondition, SoeOptional}, {sfCancelAfter, SoeOptional}, {sfFinishAfter, SoeOptional}, + {sfBytecode, SoeOptional}, + {sfData, SoeOptional}, {sfSourceTag, SoeOptional}, {sfDestinationTag, SoeOptional}, {sfOwnerNode, SoeRequired}, diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 0a88a6a191..2cf35743ae 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -123,6 +123,11 @@ TYPED_SFIELD(sfIssuerKeyEpoch, UINT32, 77) TYPED_SFIELD(sfAuditorKeyEpoch, UINT32, 78) TYPED_SFIELD(sfIssuerKeyMirrorEpoch, UINT32, 79) TYPED_SFIELD(sfAuditorKeyMirrorEpoch, UINT32, 80) +TYPED_SFIELD(sfGasLimit, UINT32, 81) +TYPED_SFIELD(sfBytecodeSizeLimit, UINT32, 82) +TYPED_SFIELD(sfGasPrice, UINT32, 83) +TYPED_SFIELD(sfGas, UINT32, 84) +TYPED_SFIELD(sfGasUsed, UINT32, 85) // 64-bit integers (common) TYPED_SFIELD(sfIndexNext, UINT64, 1) @@ -238,6 +243,7 @@ TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::kSmdNeedsAsset // 32-bit signed (common) TYPED_SFIELD(sfLoanScale, INT32, 1) TYPED_SFIELD(sfRemainingOwnerCountDelta, INT32, 2) +TYPED_SFIELD(sfVMReturnCode, INT32, 3) // currency amount (common) TYPED_SFIELD(sfAmount, AMOUNT, 1) @@ -322,6 +328,7 @@ TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43) TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44) TYPED_SFIELD(sfAmountCommitment, VL, 45) TYPED_SFIELD(sfBalanceCommitment, VL, 46) +TYPED_SFIELD(sfBytecode, VL, 47) // account (common) TYPED_SFIELD(sfAccount, ACCOUNT, 1) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index dbf9b66ac7..454aa85ffd 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -66,11 +66,13 @@ TRANSACTION(ttPAYMENT, 0, Payment, #endif TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({.delegable = Delegation::Delegable}), ({ {sfDestination, SoeRequired}, + {sfDestinationTag, SoeOptional}, {sfAmount, SoeRequired, SoeMptSupported}, {sfCondition, SoeOptional}, {sfCancelAfter, SoeOptional}, {sfFinishAfter, SoeOptional}, - {sfDestinationTag, SoeOptional}, + {sfBytecode, SoeOptional}, + {sfData, SoeOptional}, })) /** This transaction type completes an existing escrow. */ @@ -83,6 +85,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({.delegable = Delegation::Delegab {sfFulfillment, SoeOptional}, {sfCondition, SoeOptional}, {sfCredentialIDs, SoeOptional}, + {sfGas, SoeOptional}, })) @@ -1161,6 +1164,10 @@ TRANSACTION(ttFEE, 101, SetFee, {sfBaseFeeDrops, SoeOptional}, {sfReserveBaseDrops, SoeOptional}, {sfReserveIncrementDrops, SoeOptional}, + // Smart Escrow fields + {sfGasLimit, SoeOptional}, + {sfBytecodeSizeLimit, SoeOptional}, + {sfGasPrice, SoeOptional}, })) /** This system-generated transaction type is used to update the network's negative UNL diff --git a/include/xrpl/protocol_autogen/ledger_entries/Escrow.h b/include/xrpl/protocol_autogen/ledger_entries/Escrow.h index 106d69722f..fc01c8b858 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Escrow.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Escrow.h @@ -174,6 +174,54 @@ public: return this->sle_->isFieldPresent(sfFinishAfter); } + /** + * @brief Get sfBytecode (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getBytecode() const + { + if (hasBytecode()) + return this->sle_->at(sfBytecode); + return std::nullopt; + } + + /** + * @brief Check if sfBytecode is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasBytecode() const + { + return this->sle_->isFieldPresent(sfBytecode); + } + + /** + * @brief Get sfData (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getData() const + { + if (hasData()) + return this->sle_->at(sfData); + return std::nullopt; + } + + /** + * @brief Check if sfData is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasData() const + { + return this->sle_->isFieldPresent(sfData); + } + /** * @brief Get sfSourceTag (SoeOptional) * @return The field value, or std::nullopt if not present. @@ -453,6 +501,28 @@ public: return *this; } + /** + * @brief Set sfBytecode (SoeOptional) + * @return Reference to this builder for method chaining. + */ + EscrowBuilder& + setBytecode(std::decay_t const& value) + { + object_[sfBytecode] = value; + return *this; + } + + /** + * @brief Set sfData (SoeOptional) + * @return Reference to this builder for method chaining. + */ + EscrowBuilder& + setData(std::decay_t const& value) + { + object_[sfData] = value; + return *this; + } + /** * @brief Set sfSourceTag (SoeOptional) * @return Reference to this builder for method chaining. diff --git a/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h b/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h index 21478f749d..826ad6983e 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h +++ b/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h @@ -213,6 +213,78 @@ public: return this->sle_->isFieldPresent(sfReserveIncrementDrops); } + /** + * @brief Get sfGasLimit (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getGasLimit() const + { + if (hasGasLimit()) + return this->sle_->at(sfGasLimit); + return std::nullopt; + } + + /** + * @brief Check if sfGasLimit is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasGasLimit() const + { + return this->sle_->isFieldPresent(sfGasLimit); + } + + /** + * @brief Get sfBytecodeSizeLimit (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getBytecodeSizeLimit() const + { + if (hasBytecodeSizeLimit()) + return this->sle_->at(sfBytecodeSizeLimit); + return std::nullopt; + } + + /** + * @brief Check if sfBytecodeSizeLimit is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasBytecodeSizeLimit() const + { + return this->sle_->isFieldPresent(sfBytecodeSizeLimit); + } + + /** + * @brief Get sfGasPrice (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getGasPrice() const + { + if (hasGasPrice()) + return this->sle_->at(sfGasPrice); + return std::nullopt; + } + + /** + * @brief Check if sfGasPrice is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasGasPrice() const + { + return this->sle_->isFieldPresent(sfGasPrice); + } + /** * @brief Get sfPreviousTxnID (SoeOptional) * @return The field value, or std::nullopt if not present. @@ -375,6 +447,39 @@ public: return *this; } + /** + * @brief Set sfGasLimit (SoeOptional) + * @return Reference to this builder for method chaining. + */ + FeeSettingsBuilder& + setGasLimit(std::decay_t const& value) + { + object_[sfGasLimit] = value; + return *this; + } + + /** + * @brief Set sfBytecodeSizeLimit (SoeOptional) + * @return Reference to this builder for method chaining. + */ + FeeSettingsBuilder& + setBytecodeSizeLimit(std::decay_t const& value) + { + object_[sfBytecodeSizeLimit] = value; + return *this; + } + + /** + * @brief Set sfGasPrice (SoeOptional) + * @return Reference to this builder for method chaining. + */ + FeeSettingsBuilder& + setGasPrice(std::decay_t const& value) + { + object_[sfGasPrice] = value; + return *this; + } + /** * @brief Set sfPreviousTxnID (SoeOptional) * @return Reference to this builder for method chaining. diff --git a/include/xrpl/protocol_autogen/transactions/EscrowCreate.h b/include/xrpl/protocol_autogen/transactions/EscrowCreate.h index 3d28a12cee..78f9f00033 100644 --- a/include/xrpl/protocol_autogen/transactions/EscrowCreate.h +++ b/include/xrpl/protocol_autogen/transactions/EscrowCreate.h @@ -58,6 +58,32 @@ public: return this->tx_->at(sfDestination); } + /** + * @brief Get sfDestinationTag (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getDestinationTag() const + { + if (hasDestinationTag()) + { + return this->tx_->at(sfDestinationTag); + } + return std::nullopt; + } + + /** + * @brief Check if sfDestinationTag is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasDestinationTag() const + { + return this->tx_->isFieldPresent(sfDestinationTag); + } + /** * @brief Get sfAmount (SoeRequired) * @note This field supports MPT (Multi-Purpose Token) amounts. @@ -149,29 +175,55 @@ public: } /** - * @brief Get sfDestinationTag (SoeOptional) + * @brief Get sfBytecode (SoeOptional) * @return The field value, or std::nullopt if not present. */ [[nodiscard]] - protocol_autogen::Optional - getDestinationTag() const + protocol_autogen::Optional + getBytecode() const { - if (hasDestinationTag()) + if (hasBytecode()) { - return this->tx_->at(sfDestinationTag); + return this->tx_->at(sfBytecode); } return std::nullopt; } /** - * @brief Check if sfDestinationTag is present. + * @brief Check if sfBytecode is present. * @return True if the field is present, false otherwise. */ [[nodiscard]] bool - hasDestinationTag() const + hasBytecode() const { - return this->tx_->isFieldPresent(sfDestinationTag); + return this->tx_->isFieldPresent(sfBytecode); + } + + /** + * @brief Get sfData (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getData() const + { + if (hasData()) + { + return this->tx_->at(sfData); + } + return std::nullopt; + } + + /** + * @brief Check if sfData is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasData() const + { + return this->tx_->isFieldPresent(sfData); } }; @@ -232,6 +284,17 @@ public: return *this; } + /** + * @brief Set sfDestinationTag (SoeOptional) + * @return Reference to this builder for method chaining. + */ + EscrowCreateBuilder& + setDestinationTag(std::decay_t const& value) + { + object_[sfDestinationTag] = value; + return *this; + } + /** * @brief Set sfAmount (SoeRequired) * @note This field supports MPT (Multi-Purpose Token) amounts. @@ -278,13 +341,24 @@ public: } /** - * @brief Set sfDestinationTag (SoeOptional) + * @brief Set sfBytecode (SoeOptional) * @return Reference to this builder for method chaining. */ EscrowCreateBuilder& - setDestinationTag(std::decay_t const& value) + setBytecode(std::decay_t const& value) { - object_[sfDestinationTag] = value; + object_[sfBytecode] = value; + return *this; + } + + /** + * @brief Set sfData (SoeOptional) + * @return Reference to this builder for method chaining. + */ + EscrowCreateBuilder& + setData(std::decay_t const& value) + { + object_[sfData] = value; return *this; } diff --git a/include/xrpl/protocol_autogen/transactions/EscrowFinish.h b/include/xrpl/protocol_autogen/transactions/EscrowFinish.h index 1cbc60c738..9c24c7671b 100644 --- a/include/xrpl/protocol_autogen/transactions/EscrowFinish.h +++ b/include/xrpl/protocol_autogen/transactions/EscrowFinish.h @@ -146,6 +146,32 @@ public: { return this->tx_->isFieldPresent(sfCredentialIDs); } + + /** + * @brief Get sfGas (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getGas() const + { + if (hasGas()) + { + return this->tx_->at(sfGas); + } + return std::nullopt; + } + + /** + * @brief Check if sfGas is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasGas() const + { + return this->tx_->isFieldPresent(sfGas); + } }; /** @@ -249,6 +275,17 @@ public: return *this; } + /** + * @brief Set sfGas (SoeOptional) + * @return Reference to this builder for method chaining. + */ + EscrowFinishBuilder& + setGas(std::decay_t const& value) + { + object_[sfGas] = value; + return *this; + } + /** * @brief Build and return the EscrowFinish wrapper. * @param publicKey The public key for signing. diff --git a/include/xrpl/protocol_autogen/transactions/SetFee.h b/include/xrpl/protocol_autogen/transactions/SetFee.h index 9513723e94..edcea7b734 100644 --- a/include/xrpl/protocol_autogen/transactions/SetFee.h +++ b/include/xrpl/protocol_autogen/transactions/SetFee.h @@ -254,6 +254,84 @@ public: { return this->tx_->isFieldPresent(sfReserveIncrementDrops); } + + /** + * @brief Get sfGasLimit (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getGasLimit() const + { + if (hasGasLimit()) + { + return this->tx_->at(sfGasLimit); + } + return std::nullopt; + } + + /** + * @brief Check if sfGasLimit is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasGasLimit() const + { + return this->tx_->isFieldPresent(sfGasLimit); + } + + /** + * @brief Get sfBytecodeSizeLimit (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getBytecodeSizeLimit() const + { + if (hasBytecodeSizeLimit()) + { + return this->tx_->at(sfBytecodeSizeLimit); + } + return std::nullopt; + } + + /** + * @brief Check if sfBytecodeSizeLimit is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasBytecodeSizeLimit() const + { + return this->tx_->isFieldPresent(sfBytecodeSizeLimit); + } + + /** + * @brief Get sfGasPrice (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getGasPrice() const + { + if (hasGasPrice()) + { + return this->tx_->at(sfGasPrice); + } + return std::nullopt; + } + + /** + * @brief Check if sfGasPrice is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasGasPrice() const + { + return this->tx_->isFieldPresent(sfGasPrice); + } }; /** @@ -386,6 +464,39 @@ public: return *this; } + /** + * @brief Set sfGasLimit (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SetFeeBuilder& + setGasLimit(std::decay_t const& value) + { + object_[sfGasLimit] = value; + return *this; + } + + /** + * @brief Set sfBytecodeSizeLimit (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SetFeeBuilder& + setBytecodeSizeLimit(std::decay_t const& value) + { + object_[sfBytecodeSizeLimit] = value; + return *this; + } + + /** + * @brief Set sfGasPrice (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SetFeeBuilder& + setGasPrice(std::decay_t const& value) + { + object_[sfGasPrice] = value; + return *this; + } + /** * @brief Build and return the SetFee wrapper. * @param publicKey The public key for signing. diff --git a/src/libxrpl/protocol/STValidation.cpp b/src/libxrpl/protocol/STValidation.cpp index 1656aad3a2..9fdb6e4cee 100644 --- a/src/libxrpl/protocol/STValidation.cpp +++ b/src/libxrpl/protocol/STValidation.cpp @@ -59,6 +59,10 @@ STValidation::validationFormat() {sfBaseFeeDrops, SoeOptional}, {sfReserveBaseDrops, SoeOptional}, {sfReserveIncrementDrops, SoeOptional}, + // featureSmartEscrow + {sfGasLimit, SoeOptional}, + {sfBytecodeSizeLimit, SoeOptional}, + {sfGasPrice, SoeOptional}, }; // clang-format on diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index c2167d58ce..c6ebe98642 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -108,6 +108,8 @@ transResults() MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."), MAKE_ERROR(tecBAD_PROOF, "Proof cannot be verified"), MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Sponsor has not authorized this transaction."), + MAKE_ERROR(tecOUT_OF_GAS, "The WASM code ran out of gas during execution."), + MAKE_ERROR(tecBYTECODE_REJECTED, "The custom WASM code that was run rejected your transaction."), MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), @@ -133,6 +135,8 @@ transResults() MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."), MAKE_ERROR(tefNO_DST_PARTIAL, "Partial payment to create account not allowed."), MAKE_ERROR(tefBAD_PATH_COUNT, "Malformed: Too many paths."), + MAKE_ERROR(tefNO_BYTECODE, "There is no WASM code to run, but a WASM-specific field was included."), + MAKE_ERROR(tefBYTECODE_NOT_INCLUDED, "WASM code requires a field that was not included."), MAKE_ERROR(telLOCAL_ERROR, "Local failure."), MAKE_ERROR(telBAD_DOMAIN, "Domain too long."), @@ -204,6 +208,8 @@ transResults() MAKE_ERROR(temBAD_TRANSFER_FEE, "Malformed: Transfer fee is outside valid range."), MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."), MAKE_ERROR(temBAD_CIPHERTEXT, "Malformed: Invalid ciphertext."), + MAKE_ERROR(temINVALID_BYTECODE, "Malformed: Provided byte code is invalid."), + MAKE_ERROR(temTEMP_DISABLED, "The transaction requires logic that is currently temporarily disabled."), MAKE_ERROR(terRETRY, "Retry transaction."), MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."), diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index 0fe27fb3ba..589ad70230 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -88,8 +88,13 @@ EscrowCreate::checkExtraFeatures(PreflightContext const& ctx) // Only require featureMPTokensV1 when the escrow amount is an MPT and // fixCleanup3_2_0 is active; XRP/IOU escrows are unaffected by this gate. if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfAmount].holds()) - return ctx.rules.enabled(featureMPTokensV1); - return true; + { + if (!ctx.rules.enabled(featureMPTokensV1)) + return false; + } + + return (!ctx.tx.isFieldPresent(sfBytecode) && !ctx.tx.isFieldPresent(sfData)) || + ctx.rules.enabled(featureSmartEscrow); } template diff --git a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp index 09219b0bf1..59bec8ca4c 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp @@ -65,7 +65,13 @@ checkCondition(Slice f, Slice c) bool EscrowFinish::checkExtraFeatures(PreflightContext const& ctx) { - return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials); + if (ctx.tx.isFieldPresent(sfCredentialIDs) && !ctx.rules.enabled(featureCredentials)) + return false; + + if (ctx.tx.isFieldPresent(sfGas) && !ctx.rules.enabled(featureSmartEscrow)) + return false; + + return true; } NotTEC diff --git a/src/libxrpl/tx/transactors/system/Change.cpp b/src/libxrpl/tx/transactors/system/Change.cpp index f27855a5c8..0d50b80af4 100644 --- a/src/libxrpl/tx/transactors/system/Change.cpp +++ b/src/libxrpl/tx/transactors/system/Change.cpp @@ -123,6 +123,12 @@ Change::preclaim(PreclaimContext const& ctx) ctx.tx.isFieldPresent(sfReserveIncrementDrops)) return temDISABLED; } + // The ttFEE transaction format defines these fields as optional, + // but they are unconditionally forbidden until FeeVoteImpl is + // updated to populate them (SmartEscrow behavioral port). + if (ctx.tx.isFieldPresent(sfGasLimit) || ctx.tx.isFieldPresent(sfBytecodeSizeLimit) || + ctx.tx.isFieldPresent(sfGasPrice)) + return temDISABLED; return tesSUCCESS; case ttAMENDMENT: case ttUNL_MODIFY: diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/EscrowTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/EscrowTests.cpp index 2dbb450e28..ad5f88fd2f 100644 --- a/src/tests/libxrpl/protocol_autogen/ledger_entries/EscrowTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/EscrowTests.cpp @@ -27,6 +27,8 @@ TEST(EscrowTests, BuilderSettersRoundTrip) auto const conditionValue = canonical_VL(); auto const cancelAfterValue = canonical_UINT32(); auto const finishAfterValue = canonical_UINT32(); + auto const bytecodeValue = canonical_VL(); + auto const dataValue = canonical_VL(); auto const sourceTagValue = canonical_UINT32(); auto const destinationTagValue = canonical_UINT32(); auto const ownerNodeValue = canonical_UINT64(); @@ -49,6 +51,8 @@ TEST(EscrowTests, BuilderSettersRoundTrip) builder.setCondition(conditionValue); builder.setCancelAfter(cancelAfterValue); builder.setFinishAfter(finishAfterValue); + builder.setBytecode(bytecodeValue); + builder.setData(dataValue); builder.setSourceTag(sourceTagValue); builder.setDestinationTag(destinationTagValue); builder.setDestinationNode(destinationNodeValue); @@ -132,6 +136,22 @@ TEST(EscrowTests, BuilderSettersRoundTrip) EXPECT_TRUE(entry.hasFinishAfter()); } + { + auto const& expected = bytecodeValue; + auto const actualOpt = entry.getBytecode(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfBytecode"); + EXPECT_TRUE(entry.hasBytecode()); + } + + { + auto const& expected = dataValue; + auto const actualOpt = entry.getData(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfData"); + EXPECT_TRUE(entry.hasData()); + } + { auto const& expected = sourceTagValue; auto const actualOpt = entry.getSourceTag(); @@ -192,6 +212,8 @@ TEST(EscrowTests, BuilderFromSleRoundTrip) auto const conditionValue = canonical_VL(); auto const cancelAfterValue = canonical_UINT32(); auto const finishAfterValue = canonical_UINT32(); + auto const bytecodeValue = canonical_VL(); + auto const dataValue = canonical_VL(); auto const sourceTagValue = canonical_UINT32(); auto const destinationTagValue = canonical_UINT32(); auto const ownerNodeValue = canonical_UINT64(); @@ -210,6 +232,8 @@ TEST(EscrowTests, BuilderFromSleRoundTrip) sle->at(sfCondition) = conditionValue; sle->at(sfCancelAfter) = cancelAfterValue; sle->at(sfFinishAfter) = finishAfterValue; + sle->at(sfBytecode) = bytecodeValue; + sle->at(sfData) = dataValue; sle->at(sfSourceTag) = sourceTagValue; sle->at(sfDestinationTag) = destinationTagValue; sle->at(sfOwnerNode) = ownerNodeValue; @@ -340,6 +364,32 @@ TEST(EscrowTests, BuilderFromSleRoundTrip) expectEqualField(expected, *fromBuilderOpt, "sfFinishAfter"); } + { + auto const& expected = bytecodeValue; + + auto const fromSleOpt = entryFromSle.getBytecode(); + auto const fromBuilderOpt = entryFromBuilder.getBytecode(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfBytecode"); + expectEqualField(expected, *fromBuilderOpt, "sfBytecode"); + } + + { + auto const& expected = dataValue; + + auto const fromSleOpt = entryFromSle.getData(); + auto const fromBuilderOpt = entryFromBuilder.getData(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfData"); + expectEqualField(expected, *fromBuilderOpt, "sfData"); + } + { auto const& expected = sourceTagValue; @@ -477,6 +527,10 @@ TEST(EscrowTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(entry.getCancelAfter().has_value()); EXPECT_FALSE(entry.hasFinishAfter()); EXPECT_FALSE(entry.getFinishAfter().has_value()); + EXPECT_FALSE(entry.hasBytecode()); + EXPECT_FALSE(entry.getBytecode().has_value()); + EXPECT_FALSE(entry.hasData()); + EXPECT_FALSE(entry.getData().has_value()); EXPECT_FALSE(entry.hasSourceTag()); EXPECT_FALSE(entry.getSourceTag().has_value()); EXPECT_FALSE(entry.hasDestinationTag()); diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/FeeSettingsTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/FeeSettingsTests.cpp index 479d0c56c6..92bf25398c 100644 --- a/src/tests/libxrpl/protocol_autogen/ledger_entries/FeeSettingsTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/FeeSettingsTests.cpp @@ -27,6 +27,9 @@ TEST(FeeSettingsTests, BuilderSettersRoundTrip) auto const baseFeeDropsValue = canonical_AMOUNT(); auto const reserveBaseDropsValue = canonical_AMOUNT(); auto const reserveIncrementDropsValue = canonical_AMOUNT(); + auto const gasLimitValue = canonical_UINT32(); + auto const bytecodeSizeLimitValue = canonical_UINT32(); + auto const gasPriceValue = canonical_UINT32(); auto const previousTxnIDValue = canonical_UINT256(); auto const previousTxnLgrSeqValue = canonical_UINT32(); @@ -40,6 +43,9 @@ TEST(FeeSettingsTests, BuilderSettersRoundTrip) builder.setBaseFeeDrops(baseFeeDropsValue); builder.setReserveBaseDrops(reserveBaseDropsValue); builder.setReserveIncrementDrops(reserveIncrementDropsValue); + builder.setGasLimit(gasLimitValue); + builder.setBytecodeSizeLimit(bytecodeSizeLimitValue); + builder.setGasPrice(gasPriceValue); builder.setPreviousTxnID(previousTxnIDValue); builder.setPreviousTxnLgrSeq(previousTxnLgrSeqValue); @@ -108,6 +114,30 @@ TEST(FeeSettingsTests, BuilderSettersRoundTrip) EXPECT_TRUE(entry.hasReserveIncrementDrops()); } + { + auto const& expected = gasLimitValue; + auto const actualOpt = entry.getGasLimit(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfGasLimit"); + EXPECT_TRUE(entry.hasGasLimit()); + } + + { + auto const& expected = bytecodeSizeLimitValue; + auto const actualOpt = entry.getBytecodeSizeLimit(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfBytecodeSizeLimit"); + EXPECT_TRUE(entry.hasBytecodeSizeLimit()); + } + + { + auto const& expected = gasPriceValue; + auto const actualOpt = entry.getGasPrice(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfGasPrice"); + EXPECT_TRUE(entry.hasGasPrice()); + } + { auto const& expected = previousTxnIDValue; auto const actualOpt = entry.getPreviousTxnID(); @@ -144,6 +174,9 @@ TEST(FeeSettingsTests, BuilderFromSleRoundTrip) auto const baseFeeDropsValue = canonical_AMOUNT(); auto const reserveBaseDropsValue = canonical_AMOUNT(); auto const reserveIncrementDropsValue = canonical_AMOUNT(); + auto const gasLimitValue = canonical_UINT32(); + auto const bytecodeSizeLimitValue = canonical_UINT32(); + auto const gasPriceValue = canonical_UINT32(); auto const previousTxnIDValue = canonical_UINT256(); auto const previousTxnLgrSeqValue = canonical_UINT32(); @@ -156,6 +189,9 @@ TEST(FeeSettingsTests, BuilderFromSleRoundTrip) sle->at(sfBaseFeeDrops) = baseFeeDropsValue; sle->at(sfReserveBaseDrops) = reserveBaseDropsValue; sle->at(sfReserveIncrementDrops) = reserveIncrementDropsValue; + sle->at(sfGasLimit) = gasLimitValue; + sle->at(sfBytecodeSizeLimit) = bytecodeSizeLimitValue; + sle->at(sfGasPrice) = gasPriceValue; sle->at(sfPreviousTxnID) = previousTxnIDValue; sle->at(sfPreviousTxnLgrSeq) = previousTxnLgrSeqValue; @@ -259,6 +295,45 @@ TEST(FeeSettingsTests, BuilderFromSleRoundTrip) expectEqualField(expected, *fromBuilderOpt, "sfReserveIncrementDrops"); } + { + auto const& expected = gasLimitValue; + + auto const fromSleOpt = entryFromSle.getGasLimit(); + auto const fromBuilderOpt = entryFromBuilder.getGasLimit(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfGasLimit"); + expectEqualField(expected, *fromBuilderOpt, "sfGasLimit"); + } + + { + auto const& expected = bytecodeSizeLimitValue; + + auto const fromSleOpt = entryFromSle.getBytecodeSizeLimit(); + auto const fromBuilderOpt = entryFromBuilder.getBytecodeSizeLimit(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfBytecodeSizeLimit"); + expectEqualField(expected, *fromBuilderOpt, "sfBytecodeSizeLimit"); + } + + { + auto const& expected = gasPriceValue; + + auto const fromSleOpt = entryFromSle.getGasPrice(); + auto const fromBuilderOpt = entryFromBuilder.getGasPrice(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfGasPrice"); + expectEqualField(expected, *fromBuilderOpt, "sfGasPrice"); + } + { auto const& expected = previousTxnIDValue; @@ -351,6 +426,12 @@ TEST(FeeSettingsTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(entry.getReserveBaseDrops().has_value()); EXPECT_FALSE(entry.hasReserveIncrementDrops()); EXPECT_FALSE(entry.getReserveIncrementDrops().has_value()); + EXPECT_FALSE(entry.hasGasLimit()); + EXPECT_FALSE(entry.getGasLimit().has_value()); + EXPECT_FALSE(entry.hasBytecodeSizeLimit()); + EXPECT_FALSE(entry.getBytecodeSizeLimit().has_value()); + EXPECT_FALSE(entry.hasGasPrice()); + EXPECT_FALSE(entry.getGasPrice().has_value()); EXPECT_FALSE(entry.hasPreviousTxnID()); EXPECT_FALSE(entry.getPreviousTxnID().has_value()); EXPECT_FALSE(entry.hasPreviousTxnLgrSeq()); diff --git a/src/tests/libxrpl/protocol_autogen/transactions/EscrowCreateTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/EscrowCreateTests.cpp index 1d62fadb20..44e3e200d8 100644 --- a/src/tests/libxrpl/protocol_autogen/transactions/EscrowCreateTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/transactions/EscrowCreateTests.cpp @@ -30,11 +30,13 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip) // Transaction-specific field values auto const destinationValue = canonical_ACCOUNT(); + auto const destinationTagValue = canonical_UINT32(); auto const amountValue = canonical_AMOUNT(); auto const conditionValue = canonical_VL(); auto const cancelAfterValue = canonical_UINT32(); auto const finishAfterValue = canonical_UINT32(); - auto const destinationTagValue = canonical_UINT32(); + auto const bytecodeValue = canonical_VL(); + auto const dataValue = canonical_VL(); EscrowCreateBuilder builder{ accountValue, @@ -45,10 +47,12 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip) }; // Set optional fields + builder.setDestinationTag(destinationTagValue); builder.setCondition(conditionValue); builder.setCancelAfter(cancelAfterValue); builder.setFinishAfter(finishAfterValue); - builder.setDestinationTag(destinationTagValue); + builder.setBytecode(bytecodeValue); + builder.setData(dataValue); auto tx = builder.build(publicKey, secretKey); @@ -78,6 +82,14 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip) } // Verify optional fields + { + auto const& expected = destinationTagValue; + auto const actualOpt = tx.getDestinationTag(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present"; + expectEqualField(expected, *actualOpt, "sfDestinationTag"); + EXPECT_TRUE(tx.hasDestinationTag()); + } + { auto const& expected = conditionValue; auto const actualOpt = tx.getCondition(); @@ -103,11 +115,19 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip) } { - auto const& expected = destinationTagValue; - auto const actualOpt = tx.getDestinationTag(); - ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present"; - expectEqualField(expected, *actualOpt, "sfDestinationTag"); - EXPECT_TRUE(tx.hasDestinationTag()); + auto const& expected = bytecodeValue; + auto const actualOpt = tx.getBytecode(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfBytecode should be present"; + expectEqualField(expected, *actualOpt, "sfBytecode"); + EXPECT_TRUE(tx.hasBytecode()); + } + + { + auto const& expected = dataValue; + auto const actualOpt = tx.getData(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfData should be present"; + expectEqualField(expected, *actualOpt, "sfData"); + EXPECT_TRUE(tx.hasData()); } } @@ -127,11 +147,13 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip) // Transaction-specific field values auto const destinationValue = canonical_ACCOUNT(); + auto const destinationTagValue = canonical_UINT32(); auto const amountValue = canonical_AMOUNT(); auto const conditionValue = canonical_VL(); auto const cancelAfterValue = canonical_UINT32(); auto const finishAfterValue = canonical_UINT32(); - auto const destinationTagValue = canonical_UINT32(); + auto const bytecodeValue = canonical_VL(); + auto const dataValue = canonical_VL(); // Build an initial transaction EscrowCreateBuilder initialBuilder{ @@ -142,10 +164,12 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip) feeValue }; + initialBuilder.setDestinationTag(destinationTagValue); initialBuilder.setCondition(conditionValue); initialBuilder.setCancelAfter(cancelAfterValue); initialBuilder.setFinishAfter(finishAfterValue); - initialBuilder.setDestinationTag(destinationTagValue); + initialBuilder.setBytecode(bytecodeValue); + initialBuilder.setData(dataValue); auto initialTx = initialBuilder.build(publicKey, secretKey); @@ -176,6 +200,13 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip) } // Verify optional fields + { + auto const& expected = destinationTagValue; + auto const actualOpt = rebuiltTx.getDestinationTag(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present"; + expectEqualField(expected, *actualOpt, "sfDestinationTag"); + } + { auto const& expected = conditionValue; auto const actualOpt = rebuiltTx.getCondition(); @@ -198,10 +229,17 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip) } { - auto const& expected = destinationTagValue; - auto const actualOpt = rebuiltTx.getDestinationTag(); - ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present"; - expectEqualField(expected, *actualOpt, "sfDestinationTag"); + auto const& expected = bytecodeValue; + auto const actualOpt = rebuiltTx.getBytecode(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfBytecode should be present"; + expectEqualField(expected, *actualOpt, "sfBytecode"); + } + + { + auto const& expected = dataValue; + auto const actualOpt = rebuiltTx.getData(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfData should be present"; + expectEqualField(expected, *actualOpt, "sfData"); } } @@ -263,14 +301,18 @@ TEST(TransactionsEscrowCreateTests, OptionalFieldsReturnNullopt) auto tx = builder.build(publicKey, secretKey); // Verify optional fields are not present + EXPECT_FALSE(tx.hasDestinationTag()); + EXPECT_FALSE(tx.getDestinationTag().has_value()); EXPECT_FALSE(tx.hasCondition()); EXPECT_FALSE(tx.getCondition().has_value()); EXPECT_FALSE(tx.hasCancelAfter()); EXPECT_FALSE(tx.getCancelAfter().has_value()); EXPECT_FALSE(tx.hasFinishAfter()); EXPECT_FALSE(tx.getFinishAfter().has_value()); - EXPECT_FALSE(tx.hasDestinationTag()); - EXPECT_FALSE(tx.getDestinationTag().has_value()); + EXPECT_FALSE(tx.hasBytecode()); + EXPECT_FALSE(tx.getBytecode().has_value()); + EXPECT_FALSE(tx.hasData()); + EXPECT_FALSE(tx.getData().has_value()); } } diff --git a/src/tests/libxrpl/protocol_autogen/transactions/EscrowFinishTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/EscrowFinishTests.cpp index f76bfffb59..e062e24813 100644 --- a/src/tests/libxrpl/protocol_autogen/transactions/EscrowFinishTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/transactions/EscrowFinishTests.cpp @@ -34,6 +34,7 @@ TEST(TransactionsEscrowFinishTests, BuilderSettersRoundTrip) auto const fulfillmentValue = canonical_VL(); auto const conditionValue = canonical_VL(); auto const credentialIDsValue = canonical_VECTOR256(); + auto const gasValue = canonical_UINT32(); EscrowFinishBuilder builder{ accountValue, @@ -47,6 +48,7 @@ TEST(TransactionsEscrowFinishTests, BuilderSettersRoundTrip) builder.setFulfillment(fulfillmentValue); builder.setCondition(conditionValue); builder.setCredentialIDs(credentialIDsValue); + builder.setGas(gasValue); auto tx = builder.build(publicKey, secretKey); @@ -100,6 +102,14 @@ TEST(TransactionsEscrowFinishTests, BuilderSettersRoundTrip) EXPECT_TRUE(tx.hasCredentialIDs()); } + { + auto const& expected = gasValue; + auto const actualOpt = tx.getGas(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGas should be present"; + expectEqualField(expected, *actualOpt, "sfGas"); + EXPECT_TRUE(tx.hasGas()); + } + } // 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper, @@ -121,6 +131,7 @@ TEST(TransactionsEscrowFinishTests, BuilderFromStTxRoundTrip) auto const fulfillmentValue = canonical_VL(); auto const conditionValue = canonical_VL(); auto const credentialIDsValue = canonical_VECTOR256(); + auto const gasValue = canonical_UINT32(); // Build an initial transaction EscrowFinishBuilder initialBuilder{ @@ -134,6 +145,7 @@ TEST(TransactionsEscrowFinishTests, BuilderFromStTxRoundTrip) initialBuilder.setFulfillment(fulfillmentValue); initialBuilder.setCondition(conditionValue); initialBuilder.setCredentialIDs(credentialIDsValue); + initialBuilder.setGas(gasValue); auto initialTx = initialBuilder.build(publicKey, secretKey); @@ -185,6 +197,13 @@ TEST(TransactionsEscrowFinishTests, BuilderFromStTxRoundTrip) expectEqualField(expected, *actualOpt, "sfCredentialIDs"); } + { + auto const& expected = gasValue; + auto const actualOpt = rebuiltTx.getGas(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGas should be present"; + expectEqualField(expected, *actualOpt, "sfGas"); + } + } // 3) Verify wrapper throws when constructed from wrong transaction type. @@ -250,6 +269,8 @@ TEST(TransactionsEscrowFinishTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(tx.getCondition().has_value()); EXPECT_FALSE(tx.hasCredentialIDs()); EXPECT_FALSE(tx.getCredentialIDs().has_value()); + EXPECT_FALSE(tx.hasGas()); + EXPECT_FALSE(tx.getGas().has_value()); } } diff --git a/src/tests/libxrpl/protocol_autogen/transactions/SetFeeTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/SetFeeTests.cpp index 9605daee9f..d7cfb469ba 100644 --- a/src/tests/libxrpl/protocol_autogen/transactions/SetFeeTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/transactions/SetFeeTests.cpp @@ -37,6 +37,9 @@ TEST(TransactionsSetFeeTests, BuilderSettersRoundTrip) auto const baseFeeDropsValue = canonical_AMOUNT(); auto const reserveBaseDropsValue = canonical_AMOUNT(); auto const reserveIncrementDropsValue = canonical_AMOUNT(); + auto const gasLimitValue = canonical_UINT32(); + auto const bytecodeSizeLimitValue = canonical_UINT32(); + auto const gasPriceValue = canonical_UINT32(); SetFeeBuilder builder{ accountValue, @@ -53,6 +56,9 @@ TEST(TransactionsSetFeeTests, BuilderSettersRoundTrip) builder.setBaseFeeDrops(baseFeeDropsValue); builder.setReserveBaseDrops(reserveBaseDropsValue); builder.setReserveIncrementDrops(reserveIncrementDropsValue); + builder.setGasLimit(gasLimitValue); + builder.setBytecodeSizeLimit(bytecodeSizeLimitValue); + builder.setGasPrice(gasPriceValue); auto tx = builder.build(publicKey, secretKey); @@ -134,6 +140,30 @@ TEST(TransactionsSetFeeTests, BuilderSettersRoundTrip) EXPECT_TRUE(tx.hasReserveIncrementDrops()); } + { + auto const& expected = gasLimitValue; + auto const actualOpt = tx.getGasLimit(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGasLimit should be present"; + expectEqualField(expected, *actualOpt, "sfGasLimit"); + EXPECT_TRUE(tx.hasGasLimit()); + } + + { + auto const& expected = bytecodeSizeLimitValue; + auto const actualOpt = tx.getBytecodeSizeLimit(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfBytecodeSizeLimit should be present"; + expectEqualField(expected, *actualOpt, "sfBytecodeSizeLimit"); + EXPECT_TRUE(tx.hasBytecodeSizeLimit()); + } + + { + auto const& expected = gasPriceValue; + auto const actualOpt = tx.getGasPrice(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGasPrice should be present"; + expectEqualField(expected, *actualOpt, "sfGasPrice"); + EXPECT_TRUE(tx.hasGasPrice()); + } + } // 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper, @@ -158,6 +188,9 @@ TEST(TransactionsSetFeeTests, BuilderFromStTxRoundTrip) auto const baseFeeDropsValue = canonical_AMOUNT(); auto const reserveBaseDropsValue = canonical_AMOUNT(); auto const reserveIncrementDropsValue = canonical_AMOUNT(); + auto const gasLimitValue = canonical_UINT32(); + auto const bytecodeSizeLimitValue = canonical_UINT32(); + auto const gasPriceValue = canonical_UINT32(); // Build an initial transaction SetFeeBuilder initialBuilder{ @@ -174,6 +207,9 @@ TEST(TransactionsSetFeeTests, BuilderFromStTxRoundTrip) initialBuilder.setBaseFeeDrops(baseFeeDropsValue); initialBuilder.setReserveBaseDrops(reserveBaseDropsValue); initialBuilder.setReserveIncrementDrops(reserveIncrementDropsValue); + initialBuilder.setGasLimit(gasLimitValue); + initialBuilder.setBytecodeSizeLimit(bytecodeSizeLimitValue); + initialBuilder.setGasPrice(gasPriceValue); auto initialTx = initialBuilder.build(publicKey, secretKey); @@ -248,6 +284,27 @@ TEST(TransactionsSetFeeTests, BuilderFromStTxRoundTrip) expectEqualField(expected, *actualOpt, "sfReserveIncrementDrops"); } + { + auto const& expected = gasLimitValue; + auto const actualOpt = rebuiltTx.getGasLimit(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGasLimit should be present"; + expectEqualField(expected, *actualOpt, "sfGasLimit"); + } + + { + auto const& expected = bytecodeSizeLimitValue; + auto const actualOpt = rebuiltTx.getBytecodeSizeLimit(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfBytecodeSizeLimit should be present"; + expectEqualField(expected, *actualOpt, "sfBytecodeSizeLimit"); + } + + { + auto const& expected = gasPriceValue; + auto const actualOpt = rebuiltTx.getGasPrice(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGasPrice should be present"; + expectEqualField(expected, *actualOpt, "sfGasPrice"); + } + } // 3) Verify wrapper throws when constructed from wrong transaction type. @@ -319,6 +376,12 @@ TEST(TransactionsSetFeeTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(tx.getReserveBaseDrops().has_value()); EXPECT_FALSE(tx.hasReserveIncrementDrops()); EXPECT_FALSE(tx.getReserveIncrementDrops().has_value()); + EXPECT_FALSE(tx.hasGasLimit()); + EXPECT_FALSE(tx.getGasLimit().has_value()); + EXPECT_FALSE(tx.hasBytecodeSizeLimit()); + EXPECT_FALSE(tx.getBytecodeSizeLimit().has_value()); + EXPECT_FALSE(tx.hasGasPrice()); + EXPECT_FALSE(tx.getGasPrice().has_value()); } }