feat: Apply .macro changes from ripple/smart-escrow (#8157)

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Mayukha Vadari
2026-09-09 20:05:58 +00:00
committed by GitHub
parent 21890d9daf
commit 028783661d
20 changed files with 744 additions and 30 deletions

View File

@@ -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,
};
//------------------------------------------------------------------------------

View File

@@ -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)

View File

@@ -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},

View File

@@ -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)

View File

@@ -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

View File

@@ -174,6 +174,54 @@ public:
return this->sle_->isFieldPresent(sfFinishAfter);
}
/**
* @brief Get sfBytecode (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getBytecode() const
{
if (hasBytecode())
return this->sle_->at(sfBytecode);
return std::nullopt;
}
/**
* @brief Check if sfBytecode is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasBytecode() const
{
return this->sle_->isFieldPresent(sfBytecode);
}
/**
* @brief Get sfData (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getData() const
{
if (hasData())
return this->sle_->at(sfData);
return std::nullopt;
}
/**
* @brief Check if sfData is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasData() const
{
return this->sle_->isFieldPresent(sfData);
}
/**
* @brief Get sfSourceTag (SoeOptional)
* @return The field value, or std::nullopt if not present.
@@ -453,6 +501,28 @@ public:
return *this;
}
/**
* @brief Set sfBytecode (SoeOptional)
* @return Reference to this builder for method chaining.
*/
EscrowBuilder&
setBytecode(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfBytecode] = value;
return *this;
}
/**
* @brief Set sfData (SoeOptional)
* @return Reference to this builder for method chaining.
*/
EscrowBuilder&
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfData] = value;
return *this;
}
/**
* @brief Set sfSourceTag (SoeOptional)
* @return Reference to this builder for method chaining.

View File

@@ -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<SF_UINT32::type::value_type>
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<SF_UINT32::type::value_type>
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<SF_UINT32::type::value_type>
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<typename SF_UINT32::type::value_type> const& value)
{
object_[sfGasLimit] = value;
return *this;
}
/**
* @brief Set sfBytecodeSizeLimit (SoeOptional)
* @return Reference to this builder for method chaining.
*/
FeeSettingsBuilder&
setBytecodeSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfBytecodeSizeLimit] = value;
return *this;
}
/**
* @brief Set sfGasPrice (SoeOptional)
* @return Reference to this builder for method chaining.
*/
FeeSettingsBuilder&
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfGasPrice] = value;
return *this;
}
/**
* @brief Set sfPreviousTxnID (SoeOptional)
* @return Reference to this builder for method chaining.

View File

@@ -58,6 +58,32 @@ public:
return this->tx_->at(sfDestination);
}
/**
* @brief Get sfDestinationTag (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getDestinationTag() const
{
if (hasDestinationTag())
{
return this->tx_->at(sfDestinationTag);
}
return std::nullopt;
}
/**
* @brief Check if sfDestinationTag is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasDestinationTag() const
{
return this->tx_->isFieldPresent(sfDestinationTag);
}
/**
* @brief Get sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
@@ -149,29 +175,55 @@ public:
}
/**
* @brief Get sfDestinationTag (SoeOptional)
* @brief Get sfBytecode (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getDestinationTag() const
protocol_autogen::Optional<SF_VL::type::value_type>
getBytecode() const
{
if (hasDestinationTag())
if (hasBytecode())
{
return this->tx_->at(sfDestinationTag);
return this->tx_->at(sfBytecode);
}
return std::nullopt;
}
/**
* @brief Check if sfDestinationTag is present.
* @brief Check if sfBytecode is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasDestinationTag() const
hasBytecode() const
{
return this->tx_->isFieldPresent(sfDestinationTag);
return this->tx_->isFieldPresent(sfBytecode);
}
/**
* @brief Get sfData (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getData() const
{
if (hasData())
{
return this->tx_->at(sfData);
}
return std::nullopt;
}
/**
* @brief Check if sfData is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasData() const
{
return this->tx_->isFieldPresent(sfData);
}
};
@@ -232,6 +284,17 @@ public:
return *this;
}
/**
* @brief Set sfDestinationTag (SoeOptional)
* @return Reference to this builder for method chaining.
*/
EscrowCreateBuilder&
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfDestinationTag] = value;
return *this;
}
/**
* @brief Set sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
@@ -278,13 +341,24 @@ public:
}
/**
* @brief Set sfDestinationTag (SoeOptional)
* @brief Set sfBytecode (SoeOptional)
* @return Reference to this builder for method chaining.
*/
EscrowCreateBuilder&
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
setBytecode(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfDestinationTag] = value;
object_[sfBytecode] = value;
return *this;
}
/**
* @brief Set sfData (SoeOptional)
* @return Reference to this builder for method chaining.
*/
EscrowCreateBuilder&
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfData] = value;
return *this;
}

View File

@@ -146,6 +146,32 @@ public:
{
return this->tx_->isFieldPresent(sfCredentialIDs);
}
/**
* @brief Get sfGas (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getGas() const
{
if (hasGas())
{
return this->tx_->at(sfGas);
}
return std::nullopt;
}
/**
* @brief Check if sfGas is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasGas() const
{
return this->tx_->isFieldPresent(sfGas);
}
};
/**
@@ -249,6 +275,17 @@ public:
return *this;
}
/**
* @brief Set sfGas (SoeOptional)
* @return Reference to this builder for method chaining.
*/
EscrowFinishBuilder&
setGas(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfGas] = value;
return *this;
}
/**
* @brief Build and return the EscrowFinish wrapper.
* @param publicKey The public key for signing.

View File

@@ -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<SF_UINT32::type::value_type>
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<SF_UINT32::type::value_type>
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<SF_UINT32::type::value_type>
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<typename SF_UINT32::type::value_type> const& value)
{
object_[sfGasLimit] = value;
return *this;
}
/**
* @brief Set sfBytecodeSizeLimit (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SetFeeBuilder&
setBytecodeSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfBytecodeSizeLimit] = value;
return *this;
}
/**
* @brief Set sfGasPrice (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SetFeeBuilder&
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfGasPrice] = value;
return *this;
}
/**
* @brief Build and return the SetFee wrapper.
* @param publicKey The public key for signing.

View File

@@ -59,6 +59,10 @@ STValidation::validationFormat()
{sfBaseFeeDrops, SoeOptional},
{sfReserveBaseDrops, SoeOptional},
{sfReserveIncrementDrops, SoeOptional},
// featureSmartEscrow
{sfGasLimit, SoeOptional},
{sfBytecodeSizeLimit, SoeOptional},
{sfGasPrice, SoeOptional},
};
// clang-format on

View File

@@ -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."),

View File

@@ -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<MPTIssue>())
return ctx.rules.enabled(featureMPTokensV1);
return true;
{
if (!ctx.rules.enabled(featureMPTokensV1))
return false;
}
return (!ctx.tx.isFieldPresent(sfBytecode) && !ctx.tx.isFieldPresent(sfData)) ||
ctx.rules.enabled(featureSmartEscrow);
}
template <ValidIssueType T>

View File

@@ -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

View File

@@ -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:

View File

@@ -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());

View File

@@ -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());

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}