mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-27 23:38:08 +00:00
featureRepo
This commit is contained in:
85
include/xrpl/ledger/helpers/RepoHelpers.h
Normal file
85
include/xrpl/ledger/helpers/RepoHelpers.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
namespace repo {
|
||||
|
||||
/**
|
||||
* Seconds in a year, the denominator of the annualized interest rate.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A repo is active once the buyer has accepted it, which is recorded by
|
||||
* sfStartDate. Before that it is a pending offer.
|
||||
*/
|
||||
[[nodiscard]] inline bool
|
||||
isActive(SLE::const_ref sleRepo)
|
||||
{
|
||||
return sleRepo->isFieldPresent(sfStartDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount the seller owes to repurchase the collateral.
|
||||
*
|
||||
* PurchasePrice * (1 + InterestRate * elapsed / kSecondsInYear), where elapsed
|
||||
* runs from StartDate to the close time, capped at MaturityDate so the seller
|
||||
* never pays for time past maturity. Computed in Number and rounded up, so
|
||||
* rounding never favours the seller.
|
||||
*/
|
||||
[[nodiscard]] STAmount
|
||||
repurchaseAmount(SLE::const_ref sleRepo, std::uint32_t closeTime);
|
||||
|
||||
/**
|
||||
* Lock the collateral out of the seller's spendable balance.
|
||||
*
|
||||
* XRP is deducted from the account balance by the caller; this handles the
|
||||
* issued-asset cases the same way an escrow does.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
lockCollateral(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& seller,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal);
|
||||
|
||||
/**
|
||||
* The XLS-85 checks that decide whether collateral may be locked at all.
|
||||
*
|
||||
* Both parties are checked, not just the seller: the buyer receives the
|
||||
* collateral if the repo defaults, so an unauthorized or frozen buyer would
|
||||
* leave the collateral unable to move at exactly the moment it must.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
checkCollateral(
|
||||
ReadView const& view,
|
||||
AccountID const& seller,
|
||||
AccountID const& buyer,
|
||||
STAmount const& collateral,
|
||||
beast::Journal journal);
|
||||
|
||||
/**
|
||||
* Return the locked collateral to an account and remove the entry.
|
||||
*
|
||||
* Used by cancel, close and default; they differ only in who receives the
|
||||
* collateral and whether any cash moved first.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
releaseAndDelete(
|
||||
ApplyViewContext ctx,
|
||||
SLE::ref sleRepo,
|
||||
AccountID const& receiver,
|
||||
beast::Journal journal);
|
||||
|
||||
} // namespace repo
|
||||
} // namespace xrpl
|
||||
@@ -366,6 +366,18 @@ vault(uint256 const& vaultKey)
|
||||
Keylet
|
||||
loanBroker(AccountID const& owner, SeqProxy const& seq) noexcept;
|
||||
|
||||
/**
|
||||
* A repurchase agreement, keyed by the seller and the creating sequence.
|
||||
*/
|
||||
Keylet
|
||||
repo(AccountID const& seller, SeqProxy const& seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
repo(uint256 const& repoID)
|
||||
{
|
||||
return {ltREPO, repoID};
|
||||
}
|
||||
|
||||
inline Keylet
|
||||
loanBroker(uint256 const& key)
|
||||
{
|
||||
|
||||
@@ -377,6 +377,8 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecNO_SPONSOR_PERMISSION = 200,
|
||||
tecOUT_OF_GAS = 201,
|
||||
tecBYTECODE_REJECTED = 202,
|
||||
tecREPO_ACTIVE = 211,
|
||||
tecREPO_PENDING = 212,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FEATURE(Repo, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SmartEscrow, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocolV1_2, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_5_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -522,6 +522,30 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
|
||||
// no PermissionedDomainID ever (use MPTIssuance.sfDomainID)
|
||||
}))
|
||||
|
||||
/**
|
||||
* A bilateral repurchase agreement. The seller locks collateral and receives
|
||||
* cash; the entry is pending until the buyer accepts, active thereafter.
|
||||
* \sa keylet::repo
|
||||
*/
|
||||
LEDGER_ENTRY(ltREPO, 0x0095, Repo, repo, ({
|
||||
{sfAccount, SoeRequired},
|
||||
{sfCounterparty, SoeRequired},
|
||||
{sfCollateralAmount, SoeRequired, SoeMptSupported},
|
||||
{sfPurchasePrice, SoeRequired, SoeMptSupported},
|
||||
{sfInterestRate, SoeRequired},
|
||||
{sfExpiration, SoeRequired},
|
||||
{sfStartDate, SoeOptional},
|
||||
{sfMaturityDate, SoeRequired},
|
||||
{sfGracePeriod, SoeRequired},
|
||||
{sfTransferRate, SoeOptional},
|
||||
{sfData, SoeOptional},
|
||||
{sfOwnerNode, SoeRequired},
|
||||
{sfDestinationNode, SoeRequired},
|
||||
{sfIssuerNode, SoeOptional},
|
||||
{sfPreviousTxnID, SoeRequired},
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
}))
|
||||
|
||||
/** Reserve 0x0084-0x0087 for future Vault-related objects. */
|
||||
|
||||
/** A ledger object representing a loan broker
|
||||
|
||||
@@ -115,6 +115,7 @@ TYPED_SFIELD(sfConfidentialBalanceVersion, UINT32, 69)
|
||||
TYPED_SFIELD(sfSponsoredOwnerCount, UINT32, 70)
|
||||
TYPED_SFIELD(sfSponsoringOwnerCount, UINT32, 71)
|
||||
TYPED_SFIELD(sfSponsoringAccountCount, UINT32, 72)
|
||||
TYPED_SFIELD(sfMaturityDate, UINT32, 95)
|
||||
TYPED_SFIELD(sfRemainingOwnerCount, UINT32, 73)
|
||||
TYPED_SFIELD(sfSponsorFlags, UINT32, 74)
|
||||
TYPED_SFIELD(sfSubscriptionDate, UINT32, 75)
|
||||
@@ -220,6 +221,7 @@ TYPED_SFIELD(sfLoanID, UINT256, 38)
|
||||
TYPED_SFIELD(sfReferenceHolding, UINT256, 39)
|
||||
TYPED_SFIELD(sfBlindingFactor, UINT256, 40)
|
||||
TYPED_SFIELD(sfObjectID, UINT256, 41)
|
||||
TYPED_SFIELD(sfRepoID, UINT256, 47)
|
||||
|
||||
// number (common)
|
||||
TYPED_SFIELD(sfNumber, NUMBER, 1)
|
||||
@@ -282,6 +284,8 @@ TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
|
||||
TYPED_SFIELD(sfFeeAmount, AMOUNT, 32)
|
||||
TYPED_SFIELD(sfMaxFee, AMOUNT, 33)
|
||||
TYPED_SFIELD(sfFeeAmountDelta, AMOUNT, 34)
|
||||
TYPED_SFIELD(sfCollateralAmount, AMOUNT, 39)
|
||||
TYPED_SFIELD(sfPurchasePrice, AMOUNT, 40)
|
||||
|
||||
// variable length (common)
|
||||
TYPED_SFIELD(sfPublicKey, VL, 1)
|
||||
|
||||
@@ -1181,3 +1181,60 @@ TRANSACTION(ttUNL_MODIFY, 102, UNLModify,
|
||||
{sfLedgerSequence, SoeRequired},
|
||||
{sfUNLModifyValidator, SoeRequired},
|
||||
}))
|
||||
|
||||
/** This transaction type offers a repurchase agreement and locks its collateral. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/repo/RepoCreate.h>
|
||||
#endif
|
||||
TRANSACTION(ttREPO_CREATE, 114, RepoCreate,
|
||||
({.delegable = Delegation::Delegable, .amendment = featureRepo}),
|
||||
({
|
||||
{sfCounterparty, SoeRequired},
|
||||
{sfCollateralAmount, SoeRequired, SoeMptSupported},
|
||||
{sfPurchasePrice, SoeRequired, SoeMptSupported},
|
||||
{sfInterestRate, SoeRequired},
|
||||
{sfExpiration, SoeRequired},
|
||||
{sfMaturityDate, SoeRequired},
|
||||
{sfGracePeriod, SoeRequired},
|
||||
{sfData, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction type accepts a pending repo, delivering the cash. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/repo/RepoAccept.h>
|
||||
#endif
|
||||
TRANSACTION(ttREPO_ACCEPT, 115, RepoAccept,
|
||||
({.delegable = Delegation::Delegable, .amendment = featureRepo}),
|
||||
({
|
||||
{sfRepoID, SoeRequired},
|
||||
}))
|
||||
|
||||
/** This transaction type cancels a pending repo and returns the collateral. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/repo/RepoCancel.h>
|
||||
#endif
|
||||
TRANSACTION(ttREPO_CANCEL, 116, RepoCancel,
|
||||
({.delegable = Delegation::Delegable, .amendment = featureRepo}),
|
||||
({
|
||||
{sfRepoID, SoeRequired},
|
||||
}))
|
||||
|
||||
/** This transaction type repurchases the collateral, paying principal plus interest. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/repo/RepoClose.h>
|
||||
#endif
|
||||
TRANSACTION(ttREPO_CLOSE, 117, RepoClose,
|
||||
({.delegable = Delegation::Delegable, .amendment = featureRepo}),
|
||||
({
|
||||
{sfRepoID, SoeRequired},
|
||||
}))
|
||||
|
||||
/** This transaction type closes out a defaulted repo, delivering the collateral. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/repo/RepoDefault.h>
|
||||
#endif
|
||||
TRANSACTION(ttREPO_DEFAULT, 118, RepoDefault,
|
||||
({.delegable = Delegation::Delegable, .amendment = featureRepo}),
|
||||
({
|
||||
{sfRepoID, SoeRequired},
|
||||
}))
|
||||
|
||||
525
include/xrpl/protocol_autogen/ledger_entries/Repo.h
Normal file
525
include/xrpl/protocol_autogen/ledger_entries/Repo.h
Normal file
@@ -0,0 +1,525 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
|
||||
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::ledger_entries {
|
||||
|
||||
class RepoBuilder;
|
||||
|
||||
/**
|
||||
* @brief Ledger Entry: Repo
|
||||
*
|
||||
* Type: ltREPO (0x0095)
|
||||
* RPC Name: repo
|
||||
*
|
||||
* Immutable wrapper around SLE providing type-safe field access.
|
||||
* Use RepoBuilder to construct new ledger entries.
|
||||
*/
|
||||
class Repo : public LedgerEntryBase
|
||||
{
|
||||
public:
|
||||
static constexpr LedgerEntryType entryType = ltREPO;
|
||||
|
||||
/**
|
||||
* @brief Construct a Repo ledger entry wrapper from an existing SLE object.
|
||||
* @throws std::runtime_error if the ledger entry type doesn't match.
|
||||
*/
|
||||
explicit Repo(SLE::const_pointer sle)
|
||||
: LedgerEntryBase(std::move(sle))
|
||||
{
|
||||
// Verify ledger entry type
|
||||
if (sle_->getType() != entryType)
|
||||
{
|
||||
throw std::runtime_error("Invalid ledger entry type for Repo");
|
||||
}
|
||||
}
|
||||
|
||||
// Ledger entry-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfAccount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getAccount() const
|
||||
{
|
||||
return this->sle_->at(sfAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCounterparty (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getCounterparty() const
|
||||
{
|
||||
return this->sle_->at(sfCounterparty);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCollateralAmount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getCollateralAmount() const
|
||||
{
|
||||
return this->sle_->at(sfCollateralAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPurchasePrice (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getPurchasePrice() const
|
||||
{
|
||||
return this->sle_->at(sfPurchasePrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfInterestRate (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getInterestRate() const
|
||||
{
|
||||
return this->sle_->at(sfInterestRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfExpiration (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getExpiration() const
|
||||
{
|
||||
return this->sle_->at(sfExpiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfStartDate (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getStartDate() const
|
||||
{
|
||||
if (hasStartDate())
|
||||
return this->sle_->at(sfStartDate);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfStartDate is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasStartDate() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfStartDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMaturityDate (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getMaturityDate() const
|
||||
{
|
||||
return this->sle_->at(sfMaturityDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGracePeriod (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getGracePeriod() const
|
||||
{
|
||||
return this->sle_->at(sfGracePeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfTransferRate (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getTransferRate() const
|
||||
{
|
||||
if (hasTransferRate())
|
||||
return this->sle_->at(sfTransferRate);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfTransferRate is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasTransferRate() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfTransferRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 sfOwnerNode (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getOwnerNode() const
|
||||
{
|
||||
return this->sle_->at(sfOwnerNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfDestinationNode (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getDestinationNode() const
|
||||
{
|
||||
return this->sle_->at(sfDestinationNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfIssuerNode (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT64::type::value_type>
|
||||
getIssuerNode() const
|
||||
{
|
||||
if (hasIssuerNode())
|
||||
return this->sle_->at(sfIssuerNode);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfIssuerNode is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasIssuerNode() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfIssuerNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT256::type::value_type
|
||||
getPreviousTxnID() const
|
||||
{
|
||||
return this->sle_->at(sfPreviousTxnID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnLgrSeq (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getPreviousTxnLgrSeq() const
|
||||
{
|
||||
return this->sle_->at(sfPreviousTxnLgrSeq);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for Repo ledger entries.
|
||||
*
|
||||
* Provides a fluent interface for constructing ledger entries with method chaining.
|
||||
* Uses STObject internally for flexible ledger entry construction.
|
||||
* Inherits common field setters from LedgerEntryBuilderBase.
|
||||
*/
|
||||
class RepoBuilder : public LedgerEntryBuilderBase<RepoBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new RepoBuilder with required fields.
|
||||
* @param account The sfAccount field value.
|
||||
* @param counterparty The sfCounterparty field value.
|
||||
* @param collateralAmount The sfCollateralAmount field value.
|
||||
* @param purchasePrice The sfPurchasePrice field value.
|
||||
* @param interestRate The sfInterestRate field value.
|
||||
* @param expiration The sfExpiration field value.
|
||||
* @param maturityDate The sfMaturityDate field value.
|
||||
* @param gracePeriod The sfGracePeriod field value.
|
||||
* @param ownerNode The sfOwnerNode field value.
|
||||
* @param destinationNode The sfDestinationNode field value.
|
||||
* @param previousTxnID The sfPreviousTxnID field value.
|
||||
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
|
||||
*/
|
||||
RepoBuilder(std::decay_t<typename SF_ACCOUNT::type::value_type> const& account,std::decay_t<typename SF_ACCOUNT::type::value_type> const& counterparty,std::decay_t<typename SF_AMOUNT::type::value_type> const& collateralAmount,std::decay_t<typename SF_AMOUNT::type::value_type> const& purchasePrice,std::decay_t<typename SF_UINT32::type::value_type> const& interestRate,std::decay_t<typename SF_UINT32::type::value_type> const& expiration,std::decay_t<typename SF_UINT32::type::value_type> const& maturityDate,std::decay_t<typename SF_UINT32::type::value_type> const& gracePeriod,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_UINT64::type::value_type> const& destinationNode,std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq)
|
||||
: LedgerEntryBuilderBase<RepoBuilder>(ltREPO)
|
||||
{
|
||||
setAccount(account);
|
||||
setCounterparty(counterparty);
|
||||
setCollateralAmount(collateralAmount);
|
||||
setPurchasePrice(purchasePrice);
|
||||
setInterestRate(interestRate);
|
||||
setExpiration(expiration);
|
||||
setMaturityDate(maturityDate);
|
||||
setGracePeriod(gracePeriod);
|
||||
setOwnerNode(ownerNode);
|
||||
setDestinationNode(destinationNode);
|
||||
setPreviousTxnID(previousTxnID);
|
||||
setPreviousTxnLgrSeq(previousTxnLgrSeq);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a RepoBuilder from an existing SLE object.
|
||||
* @param sle The existing ledger entry to copy from.
|
||||
* @throws std::runtime_error if the ledger entry type doesn't match.
|
||||
*/
|
||||
RepoBuilder(SLE::const_pointer sle)
|
||||
{
|
||||
if (sle->at(sfLedgerEntryType) != ltREPO)
|
||||
{
|
||||
throw std::runtime_error("Invalid ledger entry type for Repo");
|
||||
}
|
||||
object_ = *sle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ledger entry-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfAccount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAccount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCounterparty (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setCounterparty(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCounterparty] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCollateralAmount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setCollateralAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCollateralAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPurchasePrice (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setPurchasePrice(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPurchasePrice] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfInterestRate (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setInterestRate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfInterestRate] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfExpiration (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setExpiration(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfExpiration] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfStartDate (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setStartDate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfStartDate] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMaturityDate (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setMaturityDate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMaturityDate] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGracePeriod (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setGracePeriod(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGracePeriod] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfTransferRate (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setTransferRate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfTransferRate] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfData (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfData] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOwnerNode (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOwnerNode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfDestinationNode (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setDestinationNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfDestinationNode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfIssuerNode (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setIssuerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfIssuerNode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPreviousTxnID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnLgrSeq (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoBuilder&
|
||||
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPreviousTxnLgrSeq] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed Repo wrapper.
|
||||
* @param index The ledger entry index.
|
||||
* @return The constructed ledger entry wrapper.
|
||||
*/
|
||||
Repo
|
||||
build(uint256 const& index)
|
||||
{
|
||||
return Repo{std::make_shared<SLE>(std::move(object_), index)};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::ledger_entries
|
||||
131
include/xrpl/protocol_autogen/transactions/RepoAccept.h
Normal file
131
include/xrpl/protocol_autogen/transactions/RepoAccept.h
Normal file
@@ -0,0 +1,131 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class RepoAcceptBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: RepoAccept
|
||||
*
|
||||
* Type: ttREPO_ACCEPT (115)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureRepo
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use RepoAcceptBuilder to construct new transactions.
|
||||
*/
|
||||
class RepoAccept : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttREPO_ACCEPT;
|
||||
|
||||
/**
|
||||
* @brief Construct a RepoAccept transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit RepoAccept(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for RepoAccept");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfRepoID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT256::type::value_type
|
||||
getRepoID() const
|
||||
{
|
||||
return this->tx_->at(sfRepoID);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for RepoAccept transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class RepoAcceptBuilder : public TransactionBuilderBase<RepoAcceptBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new RepoAcceptBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param repoID The sfRepoID field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
RepoAcceptBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_UINT256::type::value_type> const& repoID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<RepoAcceptBuilder>(ttREPO_ACCEPT, account, sequence, fee)
|
||||
{
|
||||
setRepoID(repoID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a RepoAcceptBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
RepoAcceptBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttREPO_ACCEPT)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for RepoAcceptBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfRepoID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoAcceptBuilder&
|
||||
setRepoID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfRepoID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the RepoAccept wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
RepoAccept
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return RepoAccept{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
131
include/xrpl/protocol_autogen/transactions/RepoCancel.h
Normal file
131
include/xrpl/protocol_autogen/transactions/RepoCancel.h
Normal file
@@ -0,0 +1,131 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class RepoCancelBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: RepoCancel
|
||||
*
|
||||
* Type: ttREPO_CANCEL (116)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureRepo
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use RepoCancelBuilder to construct new transactions.
|
||||
*/
|
||||
class RepoCancel : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttREPO_CANCEL;
|
||||
|
||||
/**
|
||||
* @brief Construct a RepoCancel transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit RepoCancel(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for RepoCancel");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfRepoID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT256::type::value_type
|
||||
getRepoID() const
|
||||
{
|
||||
return this->tx_->at(sfRepoID);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for RepoCancel transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class RepoCancelBuilder : public TransactionBuilderBase<RepoCancelBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new RepoCancelBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param repoID The sfRepoID field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
RepoCancelBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_UINT256::type::value_type> const& repoID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<RepoCancelBuilder>(ttREPO_CANCEL, account, sequence, fee)
|
||||
{
|
||||
setRepoID(repoID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a RepoCancelBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
RepoCancelBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttREPO_CANCEL)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for RepoCancelBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfRepoID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoCancelBuilder&
|
||||
setRepoID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfRepoID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the RepoCancel wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
RepoCancel
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return RepoCancel{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
131
include/xrpl/protocol_autogen/transactions/RepoClose.h
Normal file
131
include/xrpl/protocol_autogen/transactions/RepoClose.h
Normal file
@@ -0,0 +1,131 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class RepoCloseBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: RepoClose
|
||||
*
|
||||
* Type: ttREPO_CLOSE (117)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureRepo
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use RepoCloseBuilder to construct new transactions.
|
||||
*/
|
||||
class RepoClose : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttREPO_CLOSE;
|
||||
|
||||
/**
|
||||
* @brief Construct a RepoClose transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit RepoClose(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for RepoClose");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfRepoID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT256::type::value_type
|
||||
getRepoID() const
|
||||
{
|
||||
return this->tx_->at(sfRepoID);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for RepoClose transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class RepoCloseBuilder : public TransactionBuilderBase<RepoCloseBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new RepoCloseBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param repoID The sfRepoID field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
RepoCloseBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_UINT256::type::value_type> const& repoID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<RepoCloseBuilder>(ttREPO_CLOSE, account, sequence, fee)
|
||||
{
|
||||
setRepoID(repoID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a RepoCloseBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
RepoCloseBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttREPO_CLOSE)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for RepoCloseBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfRepoID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoCloseBuilder&
|
||||
setRepoID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfRepoID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the RepoClose wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
RepoClose
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return RepoClose{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
316
include/xrpl/protocol_autogen/transactions/RepoCreate.h
Normal file
316
include/xrpl/protocol_autogen/transactions/RepoCreate.h
Normal file
@@ -0,0 +1,316 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class RepoCreateBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: RepoCreate
|
||||
*
|
||||
* Type: ttREPO_CREATE (114)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureRepo
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use RepoCreateBuilder to construct new transactions.
|
||||
*/
|
||||
class RepoCreate : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttREPO_CREATE;
|
||||
|
||||
/**
|
||||
* @brief Construct a RepoCreate transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit RepoCreate(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for RepoCreate");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfCounterparty (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getCounterparty() const
|
||||
{
|
||||
return this->tx_->at(sfCounterparty);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCollateralAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getCollateralAmount() const
|
||||
{
|
||||
return this->tx_->at(sfCollateralAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPurchasePrice (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getPurchasePrice() const
|
||||
{
|
||||
return this->tx_->at(sfPurchasePrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfInterestRate (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getInterestRate() const
|
||||
{
|
||||
return this->tx_->at(sfInterestRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfExpiration (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getExpiration() const
|
||||
{
|
||||
return this->tx_->at(sfExpiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMaturityDate (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getMaturityDate() const
|
||||
{
|
||||
return this->tx_->at(sfMaturityDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGracePeriod (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getGracePeriod() const
|
||||
{
|
||||
return this->tx_->at(sfGracePeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for RepoCreate transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class RepoCreateBuilder : public TransactionBuilderBase<RepoCreateBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new RepoCreateBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param counterparty The sfCounterparty field value.
|
||||
* @param collateralAmount The sfCollateralAmount field value.
|
||||
* @param purchasePrice The sfPurchasePrice field value.
|
||||
* @param interestRate The sfInterestRate field value.
|
||||
* @param expiration The sfExpiration field value.
|
||||
* @param maturityDate The sfMaturityDate field value.
|
||||
* @param gracePeriod The sfGracePeriod field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
RepoCreateBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_ACCOUNT::type::value_type> const& counterparty, std::decay_t<typename SF_AMOUNT::type::value_type> const& collateralAmount, std::decay_t<typename SF_AMOUNT::type::value_type> const& purchasePrice, std::decay_t<typename SF_UINT32::type::value_type> const& interestRate, std::decay_t<typename SF_UINT32::type::value_type> const& expiration, std::decay_t<typename SF_UINT32::type::value_type> const& maturityDate, std::decay_t<typename SF_UINT32::type::value_type> const& gracePeriod, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<RepoCreateBuilder>(ttREPO_CREATE, account, sequence, fee)
|
||||
{
|
||||
setCounterparty(counterparty);
|
||||
setCollateralAmount(collateralAmount);
|
||||
setPurchasePrice(purchasePrice);
|
||||
setInterestRate(interestRate);
|
||||
setExpiration(expiration);
|
||||
setMaturityDate(maturityDate);
|
||||
setGracePeriod(gracePeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a RepoCreateBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
RepoCreateBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttREPO_CREATE)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for RepoCreateBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfCounterparty (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoCreateBuilder&
|
||||
setCounterparty(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCounterparty] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCollateralAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoCreateBuilder&
|
||||
setCollateralAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCollateralAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPurchasePrice (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoCreateBuilder&
|
||||
setPurchasePrice(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPurchasePrice] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfInterestRate (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoCreateBuilder&
|
||||
setInterestRate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfInterestRate] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfExpiration (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoCreateBuilder&
|
||||
setExpiration(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfExpiration] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMaturityDate (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoCreateBuilder&
|
||||
setMaturityDate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMaturityDate] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGracePeriod (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoCreateBuilder&
|
||||
setGracePeriod(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGracePeriod] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfData (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoCreateBuilder&
|
||||
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfData] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the RepoCreate wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
RepoCreate
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return RepoCreate{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
131
include/xrpl/protocol_autogen/transactions/RepoDefault.h
Normal file
131
include/xrpl/protocol_autogen/transactions/RepoDefault.h
Normal file
@@ -0,0 +1,131 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class RepoDefaultBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: RepoDefault
|
||||
*
|
||||
* Type: ttREPO_DEFAULT (118)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureRepo
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use RepoDefaultBuilder to construct new transactions.
|
||||
*/
|
||||
class RepoDefault : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttREPO_DEFAULT;
|
||||
|
||||
/**
|
||||
* @brief Construct a RepoDefault transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit RepoDefault(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for RepoDefault");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfRepoID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT256::type::value_type
|
||||
getRepoID() const
|
||||
{
|
||||
return this->tx_->at(sfRepoID);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for RepoDefault transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class RepoDefaultBuilder : public TransactionBuilderBase<RepoDefaultBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new RepoDefaultBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param repoID The sfRepoID field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
RepoDefaultBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_UINT256::type::value_type> const& repoID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<RepoDefaultBuilder>(ttREPO_DEFAULT, account, sequence, fee)
|
||||
{
|
||||
setRepoID(repoID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a RepoDefaultBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
RepoDefaultBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttREPO_DEFAULT)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for RepoDefaultBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfRepoID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RepoDefaultBuilder&
|
||||
setRepoID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfRepoID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the RepoDefault wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
RepoDefault
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return RepoDefault{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <xrpl/tx/invariants/NFTInvariant.h>
|
||||
#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
|
||||
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
|
||||
#include <xrpl/tx/invariants/RepoInvariant.h>
|
||||
#include <xrpl/tx/invariants/SponsorshipInvariant.h>
|
||||
#include <xrpl/tx/invariants/VaultInvariant.h>
|
||||
|
||||
@@ -449,6 +450,7 @@ using InvariantChecks = std::tuple<
|
||||
ValidClawback,
|
||||
ValidMPTIssuance,
|
||||
ValidPermissionedDomain,
|
||||
ValidRepo,
|
||||
ValidPermissionedDEX,
|
||||
ValidBookDirectory,
|
||||
ValidAMM,
|
||||
|
||||
44
include/xrpl/tx/invariants/RepoInvariant.h
Normal file
44
include/xrpl/tx/invariants/RepoInvariant.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* A repo's terms are fixed when it is struck, and it only ever moves forward.
|
||||
*
|
||||
* The economics of a repurchase agreement are agreed at create and accepted
|
||||
* against by the buyer. Nothing afterwards may restate them: not the parties,
|
||||
* not the collateral, not the price, not the rate, and not the dates. A repo
|
||||
* also moves in one direction only, from pending to active, so the start date
|
||||
* may appear once and may never change or be removed.
|
||||
*
|
||||
* A repo also leaves the ledger by exactly three paths: cancel, close and
|
||||
* default. Nothing else may delete one.
|
||||
*/
|
||||
class ValidRepo
|
||||
{
|
||||
// <before, after>. before is unseated when the entry is being created.
|
||||
std::vector<std::pair<SLE::const_pointer, SLE::const_pointer>> repos_;
|
||||
|
||||
// A repo leaves the ledger by exactly three transactions, so a deletion by
|
||||
// anything else is a defect wherever it came from.
|
||||
bool deleted_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, SLE::const_ref, SLE::const_ref);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
55
include/xrpl/tx/transactors/repo/RepoAccept.h
Normal file
55
include/xrpl/tx/transactors/repo/RepoAccept.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Accepts a pending repo, delivering the purchase price to the seller.
|
||||
*
|
||||
* Only the counterparty named at create may accept, and only before Expiration.
|
||||
* Interest runs from this ledger's close time, not from when the offer was made.
|
||||
*/
|
||||
class RepoAccept : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit RepoAccept(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
55
include/xrpl/tx/transactors/repo/RepoCancel.h
Normal file
55
include/xrpl/tx/transactors/repo/RepoCancel.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Cancels a pending repo and returns the collateral to the seller.
|
||||
*
|
||||
* The seller may cancel at any time while pending; anyone may cancel once the
|
||||
* offer has expired.
|
||||
*/
|
||||
class RepoCancel : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit RepoCancel(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
55
include/xrpl/tx/transactors/repo/RepoClose.h
Normal file
55
include/xrpl/tx/transactors/repo/RepoClose.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Repurchases the collateral by paying principal plus interest.
|
||||
*
|
||||
* The collateral unlocks only if the payment lands in the same transaction. A
|
||||
* failed payment fails the whole transaction and the collateral stays locked.
|
||||
*/
|
||||
class RepoClose : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit RepoClose(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
55
include/xrpl/tx/transactors/repo/RepoCreate.h
Normal file
55
include/xrpl/tx/transactors/repo/RepoCreate.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Offers a repurchase agreement and locks the collateral immediately.
|
||||
*
|
||||
* The buyer accepts against collateral that is already immobilized, not against
|
||||
* a promise. The entry is pending until RepoAccept.
|
||||
*/
|
||||
class RepoCreate : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit RepoCreate(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
55
include/xrpl/tx/transactors/repo/RepoDefault.h
Normal file
55
include/xrpl/tx/transactors/repo/RepoDefault.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Closes out a repo the seller did not repurchase in time.
|
||||
*
|
||||
* Submittable by anyone once the close time passes MaturityDate plus
|
||||
* GracePeriod. The collateral goes to the buyer; no cash moves.
|
||||
*/
|
||||
class RepoDefault : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit RepoDefault(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
263
src/libxrpl/ledger/helpers/RepoHelpers.cpp
Normal file
263
src/libxrpl/ledger/helpers/RepoHelpers.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
#include <xrpl/ledger/helpers/RepoHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/LendingHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <variant>
|
||||
|
||||
namespace xrpl {
|
||||
namespace repo {
|
||||
|
||||
STAmount
|
||||
repurchaseAmount(SLE::const_ref sleRepo, std::uint32_t closeTime)
|
||||
{
|
||||
STAmount const price = (*sleRepo)[sfPurchasePrice];
|
||||
std::uint32_t const rate = (*sleRepo)[sfInterestRate];
|
||||
|
||||
// A pending repo has no start date and cannot be closed, so the caller is
|
||||
// expected to have rejected that already.
|
||||
std::uint32_t const start =
|
||||
sleRepo->isFieldPresent(sfStartDate) ? sleRepo->getFieldU32(sfStartDate) : closeTime;
|
||||
std::uint32_t const maturity = (*sleRepo)[sfMaturityDate];
|
||||
|
||||
// Time past maturity is not charged: the seller owes interest to maturity
|
||||
// and no further.
|
||||
std::uint32_t const end = std::min(closeTime, maturity);
|
||||
std::uint32_t const elapsed = end > start ? end - start : 0;
|
||||
|
||||
if (rate == 0 || elapsed == 0)
|
||||
return price;
|
||||
|
||||
// The rate is annualized in 1/10 basis points, matching sfInterestRate on
|
||||
// ltLOAN, so kTenthBipsPerUnity of it is the whole of the purchase price.
|
||||
Number const interest = tenthBipsOfValue(Number(price) * Number(elapsed), TenthBips32{rate}) /
|
||||
Number(kSecondsInYear);
|
||||
|
||||
// Round up, so rounding never favours the seller.
|
||||
NumberRoundModeGuard const guard(Number::RoundingMode::Upward);
|
||||
return price + STAmount(price.asset(), interest);
|
||||
}
|
||||
|
||||
TER
|
||||
lockCollateral(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& seller,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// The issuer cannot be a party, checked at preflight, so this is defensive.
|
||||
if (issuer == seller)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (amount.holds<MPTIssue>())
|
||||
return lockEscrowMPT(view, seller, amount, journal);
|
||||
|
||||
return directSendNoFee(view, seller, issuer, amount, true, journal);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
TER
|
||||
checkIouCollateral(
|
||||
ReadView const& view,
|
||||
AccountID const& seller,
|
||||
AccountID const& buyer,
|
||||
STAmount const& collateral,
|
||||
beast::Journal journal)
|
||||
{
|
||||
auto const& issue = collateral.get<Issue>();
|
||||
AccountID const issuer = collateral.getIssuer();
|
||||
|
||||
auto const sleIssuer = view.read(keylet::account(issuer));
|
||||
if (!sleIssuer)
|
||||
return tecNO_ISSUER;
|
||||
|
||||
// The issuer has to have opted in to having its obligations locked.
|
||||
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const sleLine = view.read(keylet::trustLine(seller, issuer, issue.currency));
|
||||
if (!sleLine)
|
||||
return tecNO_LINE;
|
||||
|
||||
if (auto const ter = requireAuth(view, issue, seller); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto const ter = requireAuth(view, issue, buyer); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (isFrozen(view, seller, issue) || isFrozen(view, buyer, issue))
|
||||
return tecFROZEN;
|
||||
|
||||
STAmount const spendable =
|
||||
accountHolds(view, seller, issue.currency, issuer, FreezeHandling::IgnoreFreeze, journal);
|
||||
|
||||
if (spendable < collateral || spendable <= beast::kZero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
if (!canAdd(spendable, collateral))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
checkMptCollateral(
|
||||
ReadView const& view,
|
||||
AccountID const& seller,
|
||||
AccountID const& buyer,
|
||||
STAmount const& collateral,
|
||||
beast::Journal journal)
|
||||
{
|
||||
auto const& mptIssue = collateral.get<MPTIssue>();
|
||||
auto const issuanceKey = keylet::mptokenIssuance(mptIssue.getMptID());
|
||||
auto const sleIssuance = view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// The issuance has to permit locking, the same opt-in an escrow needs.
|
||||
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (!view.exists(keylet::mptoken(issuanceKey.key, seller)))
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (auto const ter = requireAuth(view, mptIssue, seller, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto const ter = requireAuth(view, mptIssue, buyer, AuthType::WeakAuth); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (isFrozen(view, seller, *sleIssuance) || isFrozen(view, buyer, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
if (auto const ter = canTransfer(view, mptIssue, seller, buyer); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
STAmount const spendable = accountHolds(
|
||||
view, seller, mptIssue, FreezeHandling::IgnoreFreeze, AuthHandling::IgnoreAuth, journal);
|
||||
|
||||
if (spendable < collateral || spendable <= beast::kZero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TER
|
||||
checkCollateral(
|
||||
ReadView const& view,
|
||||
AccountID const& seller,
|
||||
AccountID const& buyer,
|
||||
STAmount const& collateral,
|
||||
beast::Journal journal)
|
||||
{
|
||||
if (isXRP(collateral))
|
||||
return tesSUCCESS;
|
||||
|
||||
if (collateral.holds<MPTIssue>())
|
||||
return checkMptCollateral(view, seller, buyer, collateral, journal);
|
||||
|
||||
return checkIouCollateral(view, seller, buyer, collateral, journal);
|
||||
}
|
||||
|
||||
TER
|
||||
releaseAndDelete(
|
||||
ApplyViewContext ctx,
|
||||
SLE::ref sleRepo,
|
||||
AccountID const& receiver,
|
||||
beast::Journal journal)
|
||||
{
|
||||
auto& view = ctx.view;
|
||||
STAmount const collateral = (*sleRepo)[sfCollateralAmount];
|
||||
AccountID const seller = (*sleRepo)[sfAccount];
|
||||
AccountID const counterparty = (*sleRepo)[sfCounterparty];
|
||||
|
||||
auto const sleSeller = view.peek(keylet::account(seller));
|
||||
if (!sleSeller)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Return the collateral. XRP went out of the balance directly at create,
|
||||
// so it comes back the same way.
|
||||
if (isXRP(collateral))
|
||||
{
|
||||
auto const sleReceiver =
|
||||
receiver == seller ? sleSeller : view.peek(keylet::account(receiver));
|
||||
if (!sleReceiver)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
(*sleReceiver)[sfBalance] = (*sleReceiver)[sfBalance] + collateral;
|
||||
view.update(sleReceiver);
|
||||
}
|
||||
else
|
||||
{
|
||||
AccountID const issuer = collateral.getIssuer();
|
||||
Rate const lockedRate = sleRepo->isFieldPresent(sfTransferRate)
|
||||
? Rate{(*sleRepo)[sfTransferRate]}
|
||||
: kParityRate;
|
||||
auto const sleReceiver = view.peek(keylet::account(receiver));
|
||||
if (!sleReceiver)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx,
|
||||
lockedRate,
|
||||
sleReceiver,
|
||||
STAmount((*sleReceiver)[sfBalance]).xrp(),
|
||||
collateral,
|
||||
issuer,
|
||||
seller,
|
||||
receiver,
|
||||
true,
|
||||
journal);
|
||||
},
|
||||
collateral.asset().value());
|
||||
if (!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
// Remove from both parties' directories, and the issuer's for an IOU.
|
||||
if (!view.dirRemove(keylet::ownerDir(seller), (*sleRepo)[sfOwnerNode], sleRepo->key(), false))
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
if (!view.dirRemove(
|
||||
keylet::ownerDir(counterparty), (*sleRepo)[sfDestinationNode], sleRepo->key(), false))
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
if (sleRepo->isFieldPresent(sfIssuerNode) &&
|
||||
!view.dirRemove(
|
||||
keylet::ownerDir(collateral.getIssuer()),
|
||||
(*sleRepo)[sfIssuerNode],
|
||||
sleRepo->key(),
|
||||
false))
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
decreaseOwnerCountForObject(view, sleSeller, sleRepo, 1, journal);
|
||||
view.erase(sleRepo);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace repo
|
||||
} // namespace xrpl
|
||||
@@ -104,6 +104,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
LoanBroker = 'l', // lower-case L
|
||||
Loan = 'L',
|
||||
Sponsorship = '>',
|
||||
Repo = 'Z',
|
||||
|
||||
// No longer used or supported. Left here to reserve the space to avoid accidental reuse.
|
||||
Contract [[deprecated]] = 'c',
|
||||
@@ -610,6 +611,12 @@ permissionedDomain(uint256 const& domainID) noexcept
|
||||
return {ltPERMISSIONED_DOMAIN, domainID};
|
||||
}
|
||||
|
||||
Keylet
|
||||
repo(AccountID const& seller, SeqProxy const& seq) noexcept
|
||||
{
|
||||
return {ltREPO, indexHash(LedgerNameSpace::Repo, seller, seq.value())};
|
||||
}
|
||||
|
||||
} // namespace keylet
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -110,6 +110,8 @@ transResults()
|
||||
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(tecREPO_ACTIVE, "The repo has already been accepted."),
|
||||
MAKE_ERROR(tecREPO_PENDING, "The repo has not been accepted yet."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
|
||||
@@ -154,6 +154,10 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
|
||||
if (isXRP((*before)[sfAmount]))
|
||||
drops_ -= (*before)[sfAmount].xrp().drops();
|
||||
break;
|
||||
case ltREPO:
|
||||
if (isXRP((*before)[sfCollateralAmount]))
|
||||
drops_ -= (*before)[sfCollateralAmount].xrp().drops();
|
||||
break;
|
||||
case ltSPONSORSHIP:
|
||||
if (before->isFieldPresent(sfFeeAmount))
|
||||
{
|
||||
@@ -188,6 +192,10 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
|
||||
if (!isDelete && isXRP((*after)[sfAmount]))
|
||||
drops_ += (*after)[sfAmount].xrp().drops();
|
||||
break;
|
||||
case ltREPO:
|
||||
if (!isDelete && isXRP((*after)[sfCollateralAmount]))
|
||||
drops_ += (*after)[sfCollateralAmount].xrp().drops();
|
||||
break;
|
||||
case ltSPONSORSHIP:
|
||||
if (!isDelete && after->isFieldPresent(sfFeeAmount))
|
||||
{
|
||||
|
||||
126
src/libxrpl/tx/invariants/RepoInvariant.cpp
Normal file
126
src/libxrpl/tx/invariants/RepoInvariant.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include <xrpl/tx/invariants/RepoInvariant.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAccount.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
void
|
||||
ValidRepo::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
|
||||
{
|
||||
if (after && after->getType() == ltREPO)
|
||||
{
|
||||
repos_.emplace_back(before, after);
|
||||
if (isDelete)
|
||||
deleted_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ValidRepo::finalize(
|
||||
STTx const& tx,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// A repo leaves the ledger only by cancel, close or default.
|
||||
if (deleted_)
|
||||
{
|
||||
switch (tx.getTxnType())
|
||||
{
|
||||
case ttREPO_CANCEL:
|
||||
case ttREPO_CLOSE:
|
||||
case ttREPO_DEFAULT:
|
||||
break;
|
||||
default:
|
||||
JLOG(j.fatal()) << "Invariant failed: a repo was deleted by transaction type "
|
||||
<< tx.getTxnType();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// A repo cannot exist unless the amendment is enabled, so no separate
|
||||
// amendment check is needed here.
|
||||
for (auto const& [before, after] : repos_)
|
||||
{
|
||||
// The two parties must be distinct, or the seller would be paying
|
||||
// itself and no collateral would be at risk.
|
||||
if (after->getAccountID(sfAccount) == after->getAccountID(sfCounterparty))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: a repo's seller is its counterparty";
|
||||
return false;
|
||||
}
|
||||
|
||||
STAmount const collateral = (*after)[sfCollateralAmount];
|
||||
STAmount const price = (*after)[sfPurchasePrice];
|
||||
|
||||
if (collateral <= beast::kZero || price <= beast::kZero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: a repo's collateral or price is not positive";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Settling in the collateral asset would let the seller repurchase
|
||||
// with the very asset that is locked.
|
||||
if (collateral.asset() == price.asset())
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: a repo's collateral and cash are the same asset";
|
||||
return false;
|
||||
}
|
||||
|
||||
// The repurchase deadline must fall after the offer stops being
|
||||
// acceptable, or a repo could mature before it could be accepted.
|
||||
if ((*after)[sfMaturityDate] <= (*after)[sfExpiration])
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: a repo matures before it expires";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!before)
|
||||
continue;
|
||||
|
||||
// Terms are struck once. Nothing after create may restate them.
|
||||
auto const sameAccount = [&](SField const& f) {
|
||||
return before->getAccountID(f) == after->getAccountID(f);
|
||||
};
|
||||
auto const sameU32 = [&](SField const& f) {
|
||||
return before->getFieldU32(f) == after->getFieldU32(f);
|
||||
};
|
||||
|
||||
if (!sameAccount(sfAccount) || !sameAccount(sfCounterparty) ||
|
||||
(*before)[sfCollateralAmount] != collateral || (*before)[sfPurchasePrice] != price ||
|
||||
!sameU32(sfInterestRate) || !sameU32(sfMaturityDate) || !sameU32(sfExpiration) ||
|
||||
!sameU32(sfGracePeriod))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: a repo's terms changed after creation";
|
||||
return false;
|
||||
}
|
||||
|
||||
// A repo moves from pending to active and never back.
|
||||
bool const wasActive = before->isFieldPresent(sfStartDate);
|
||||
bool const isActiveNow = after->isFieldPresent(sfStartDate);
|
||||
|
||||
if (wasActive && !isActiveNow)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: an active repo reverted to pending";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wasActive && isActiveNow &&
|
||||
before->getFieldU32(sfStartDate) != after->getFieldU32(sfStartDate))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: a repo's start date changed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
87
src/libxrpl/tx/transactors/repo/RepoAccept.cpp
Normal file
87
src/libxrpl/tx/transactors/repo/RepoAccept.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include <xrpl/tx/transactors/repo/RepoAccept.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/RepoHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
std::uint32_t
|
||||
RepoAccept::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
return tfUniversalMask;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
RepoAccept::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
RepoAccept::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const sleRepo = ctx.view.read(keylet::repo(ctx.tx[sfRepoID]));
|
||||
if (!sleRepo)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
if (repo::isActive(sleRepo))
|
||||
return tecREPO_ACTIVE;
|
||||
|
||||
// Only the buyer named at create may accept.
|
||||
if ((*sleRepo)[sfCounterparty] != ctx.tx[sfAccount])
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (ctx.view.parentCloseTime().time_since_epoch().count() >= (*sleRepo)[sfExpiration])
|
||||
return tecEXPIRED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
RepoAccept::doApply()
|
||||
{
|
||||
auto const sleRepo = view().peek(keylet::repo(ctx_.tx[sfRepoID]));
|
||||
if (!sleRepo)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
STAmount const price = (*sleRepo)[sfPurchasePrice];
|
||||
AccountID const seller = (*sleRepo)[sfAccount];
|
||||
|
||||
// The cash leg. A failure here fails the accept, leaving the offer pending.
|
||||
if (auto const ter = accountSend(view(), accountID_, seller, price, j_); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Interest runs from when the cash actually moved.
|
||||
(*sleRepo)[sfStartDate] = view().parentCloseTime().time_since_epoch().count();
|
||||
view().update(sleRepo);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
RepoAccept::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
RepoAccept::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
76
src/libxrpl/tx/transactors/repo/RepoCancel.cpp
Normal file
76
src/libxrpl/tx/transactors/repo/RepoCancel.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include <xrpl/tx/transactors/repo/RepoCancel.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/RepoHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
std::uint32_t
|
||||
RepoCancel::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
return tfUniversalMask;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
RepoCancel::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
RepoCancel::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const sleRepo = ctx.view.read(keylet::repo(ctx.tx[sfRepoID]));
|
||||
if (!sleRepo)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
if (repo::isActive(sleRepo))
|
||||
return tecREPO_ACTIVE;
|
||||
|
||||
// The seller may withdraw the offer at any time; anyone may clear it once
|
||||
// it has expired.
|
||||
if ((*sleRepo)[sfAccount] != ctx.tx[sfAccount] &&
|
||||
ctx.view.parentCloseTime().time_since_epoch().count() < (*sleRepo)[sfExpiration])
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
RepoCancel::doApply()
|
||||
{
|
||||
auto const sleRepo = view().peek(keylet::repo(ctx_.tx[sfRepoID]));
|
||||
if (!sleRepo)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// No cash moved, so the collateral simply goes back.
|
||||
return repo::releaseAndDelete(ctx_.getApplyViewContext(), sleRepo, (*sleRepo)[sfAccount], j_);
|
||||
}
|
||||
|
||||
void
|
||||
RepoCancel::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
RepoCancel::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
87
src/libxrpl/tx/transactors/repo/RepoClose.cpp
Normal file
87
src/libxrpl/tx/transactors/repo/RepoClose.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include <xrpl/tx/transactors/repo/RepoClose.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/RepoHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
std::uint32_t
|
||||
RepoClose::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
return tfUniversalMask;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
RepoClose::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
RepoClose::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const sleRepo = ctx.view.read(keylet::repo(ctx.tx[sfRepoID]));
|
||||
if (!sleRepo)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
if (!repo::isActive(sleRepo))
|
||||
return tecREPO_PENDING;
|
||||
|
||||
// Only the seller repurchases.
|
||||
if ((*sleRepo)[sfAccount] != ctx.tx[sfAccount])
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const closeTime = ctx.view.parentCloseTime().time_since_epoch().count();
|
||||
if (closeTime > (*sleRepo)[sfMaturityDate] + (*sleRepo)[sfGracePeriod])
|
||||
return tecEXPIRED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
RepoClose::doApply()
|
||||
{
|
||||
auto const sleRepo = view().peek(keylet::repo(ctx_.tx[sfRepoID]));
|
||||
if (!sleRepo)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const closeTime = view().parentCloseTime().time_since_epoch().count();
|
||||
STAmount const owed = repo::repurchaseAmount(sleRepo, closeTime);
|
||||
AccountID const counterparty = (*sleRepo)[sfCounterparty];
|
||||
|
||||
// The gate: the collateral is released only because this payment landed.
|
||||
// If it fails, the whole transaction fails and the collateral stays locked.
|
||||
if (auto const ter = accountSend(view(), accountID_, counterparty, owed, j_);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return repo::releaseAndDelete(ctx_.getApplyViewContext(), sleRepo, accountID_, j_);
|
||||
}
|
||||
|
||||
void
|
||||
RepoClose::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
RepoClose::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
240
src/libxrpl/tx/transactors/repo/RepoCreate.cpp
Normal file
240
src/libxrpl/tx/transactors/repo/RepoCreate.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
#include <xrpl/tx/transactors/repo/RepoCreate.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RepoHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* The Data field carries a reference to the governing legal annex.
|
||||
*/
|
||||
static constexpr std::size_t kMaxRepoDataBytes = 256;
|
||||
|
||||
std::uint32_t
|
||||
RepoCreate::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
return tfUniversalMask;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
RepoCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
AccountID const account = ctx.tx[sfAccount];
|
||||
AccountID const counterparty = ctx.tx[sfCounterparty];
|
||||
|
||||
if (account == counterparty)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "RepoCreate: the counterparty is the account";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
STAmount const collateral = ctx.tx[sfCollateralAmount];
|
||||
STAmount const price = ctx.tx[sfPurchasePrice];
|
||||
|
||||
if (collateral <= beast::kZero || !isLegalNet(collateral))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "RepoCreate: the collateral amount is not positive";
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
if (price <= beast::kZero || !isLegalNet(price))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "RepoCreate: the purchase price is not positive";
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
// Settling the repurchase in the collateral itself would let the seller
|
||||
// repay with the very asset that is locked.
|
||||
if (collateral.asset() == price.asset())
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "RepoCreate: the collateral and the cash are the same asset";
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
// The repurchase deadline has to fall after the offer stops being
|
||||
// acceptable, or the repo could mature before it could be accepted.
|
||||
if (ctx.tx[sfMaturityDate] <= ctx.tx[sfExpiration])
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "RepoCreate: maturity is not after expiration";
|
||||
return temBAD_EXPIRATION;
|
||||
}
|
||||
|
||||
// The same ceiling LoanSet puts on sfInterestRate.
|
||||
if (ctx.tx[sfInterestRate] > lending::kMaxInterestRate.value())
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "RepoCreate: the interest rate is above the maximum";
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
if (auto const data = ctx.tx[~sfData]; data && data->size() > kMaxRepoDataBytes)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "RepoCreate: sfData is too large";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
RepoCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
AccountID const account = ctx.tx[sfAccount];
|
||||
AccountID const counterparty = ctx.tx[sfCounterparty];
|
||||
|
||||
if (!ctx.view.exists(keylet::account(counterparty)))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "RepoCreate: the counterparty account does not exist";
|
||||
return tecNO_DST;
|
||||
}
|
||||
|
||||
STAmount const collateral = ctx.tx[sfCollateralAmount];
|
||||
if (!isXRP(collateral))
|
||||
{
|
||||
// An issuer holding its own obligation as collateral has nothing
|
||||
// locked, so neither party may be the issuer.
|
||||
AccountID const issuer = collateral.getIssuer();
|
||||
if (issuer == account || issuer == counterparty)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "RepoCreate: the collateral issuer is a party";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (!ctx.view.exists(keylet::account(issuer)))
|
||||
return tecNO_ISSUER;
|
||||
}
|
||||
|
||||
// The XLS-85 table: the asset must be lockable, both parties authorized
|
||||
// and unfrozen, and the seller actually holding enough to lock.
|
||||
if (auto const ter = repo::checkCollateral(ctx.view, account, counterparty, collateral, ctx.j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
RepoCreate::doApply()
|
||||
{
|
||||
auto applyViewContext = ctx_.getApplyViewContext();
|
||||
|
||||
auto const sle = view().peek(keylet::account(accountID_));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
STAmount const collateral = ctx_.tx[sfCollateralAmount];
|
||||
AccountID const counterparty = ctx_.tx[sfCounterparty];
|
||||
|
||||
if (auto const ret =
|
||||
checkReserve(applyViewContext, sle, preFeeBalance_, {.ownerCountDelta = 1}, j_);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
// Locking XRP must leave the seller above its own reserve floor.
|
||||
if (isXRP(collateral))
|
||||
{
|
||||
auto const reserve = accountReserve(view(), sle, j_, {.ownerCountDelta = 1});
|
||||
if (preFeeBalance_ - collateral.xrp() < reserve)
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
Keylet const repoKeylet = keylet::repo(accountID_, ctx_.tx.getSeqProxy());
|
||||
auto const sleRepo = std::make_shared<SLE>(repoKeylet);
|
||||
(*sleRepo)[sfAccount] = accountID_;
|
||||
(*sleRepo)[sfCounterparty] = counterparty;
|
||||
(*sleRepo)[sfCollateralAmount] = collateral;
|
||||
(*sleRepo)[sfPurchasePrice] = ctx_.tx[sfPurchasePrice];
|
||||
(*sleRepo)[sfInterestRate] = ctx_.tx[sfInterestRate];
|
||||
(*sleRepo)[sfExpiration] = ctx_.tx[sfExpiration];
|
||||
(*sleRepo)[sfMaturityDate] = ctx_.tx[sfMaturityDate];
|
||||
(*sleRepo)[sfGracePeriod] = ctx_.tx[sfGracePeriod];
|
||||
(*sleRepo)[~sfData] = ctx_.tx[~sfData];
|
||||
|
||||
// The issuer's rate is captured now so a later change cannot alter the
|
||||
// economics of a trade already struck.
|
||||
if (!isXRP(collateral))
|
||||
{
|
||||
auto const xferRate = transferRate(view(), collateral);
|
||||
if (xferRate != kParityRate)
|
||||
(*sleRepo)[sfTransferRate] = xferRate.value;
|
||||
}
|
||||
|
||||
view().insert(sleRepo);
|
||||
|
||||
if (auto const page = view().dirInsert(
|
||||
keylet::ownerDir(accountID_), repoKeylet, describeOwnerDir(accountID_)))
|
||||
(*sleRepo)[sfOwnerNode] = *page;
|
||||
else
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const page = view().dirInsert(
|
||||
keylet::ownerDir(counterparty), repoKeylet, describeOwnerDir(counterparty)))
|
||||
(*sleRepo)[sfDestinationNode] = *page;
|
||||
else
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
// An IOU issuer tracks locked balances through its own directory; an MPT
|
||||
// issuance records the locked amount on the issuance itself.
|
||||
if (!isXRP(collateral) && !collateral.holds<MPTIssue>())
|
||||
{
|
||||
AccountID const issuer = collateral.getIssuer();
|
||||
if (auto const page =
|
||||
view().dirInsert(keylet::ownerDir(issuer), repoKeylet, describeOwnerDir(issuer)))
|
||||
(*sleRepo)[sfIssuerNode] = *page;
|
||||
else
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (isXRP(collateral))
|
||||
{
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - collateral;
|
||||
view().update(sle);
|
||||
}
|
||||
else if (
|
||||
auto const ret =
|
||||
repo::lockCollateral(view(), collateral.getIssuer(), accountID_, collateral, j_);
|
||||
!isTesSuccess(ret))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
increaseOwnerCount(applyViewContext, sle, 1, j_);
|
||||
addSponsorToLedgerEntry(applyViewContext, sleRepo);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
RepoCreate::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
RepoCreate::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
77
src/libxrpl/tx/transactors/repo/RepoDefault.cpp
Normal file
77
src/libxrpl/tx/transactors/repo/RepoDefault.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <xrpl/tx/transactors/repo/RepoDefault.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/RepoHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
std::uint32_t
|
||||
RepoDefault::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
return tfUniversalMask;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
RepoDefault::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
RepoDefault::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const sleRepo = ctx.view.read(keylet::repo(ctx.tx[sfRepoID]));
|
||||
if (!sleRepo)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
if (!repo::isActive(sleRepo))
|
||||
return tecREPO_PENDING;
|
||||
|
||||
// Anyone may close out a defaulted repo, but only once the seller's time
|
||||
// to repurchase has run out.
|
||||
auto const closeTime = ctx.view.parentCloseTime().time_since_epoch().count();
|
||||
if (closeTime <= (*sleRepo)[sfMaturityDate] + (*sleRepo)[sfGracePeriod])
|
||||
return tecTOO_SOON;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
RepoDefault::doApply()
|
||||
{
|
||||
auto const sleRepo = view().peek(keylet::repo(ctx_.tx[sfRepoID]));
|
||||
if (!sleRepo)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// The buyer keeps the collateral; no cash moves.
|
||||
return repo::releaseAndDelete(
|
||||
ctx_.getApplyViewContext(), sleRepo, (*sleRepo)[sfCounterparty], j_);
|
||||
}
|
||||
|
||||
void
|
||||
RepoDefault::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
RepoDefault::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
655
src/test/app/Repo_test.cpp
Normal file
655
src/test/app/Repo_test.cpp
Normal file
@@ -0,0 +1,655 @@
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/acctdelete.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/fee.h>
|
||||
#include <test/jtx/flags.h>
|
||||
#include <test/jtx/mpt.h>
|
||||
#include <test/jtx/owners.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/utility.h>
|
||||
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/ledger/helpers/LendingHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
class Repo_test : public beast::unit_test::Suite
|
||||
{
|
||||
static json::Value
|
||||
create(
|
||||
jtx::Account const& seller,
|
||||
jtx::Account const& buyer,
|
||||
STAmount const& collateral,
|
||||
STAmount const& price,
|
||||
std::uint32_t rate,
|
||||
std::uint32_t expiration,
|
||||
std::uint32_t maturity,
|
||||
std::uint32_t grace)
|
||||
{
|
||||
json::Value jv;
|
||||
jv[sfTransactionType] = jss::RepoCreate;
|
||||
jv[sfAccount] = seller.human();
|
||||
jv[sfCounterparty] = buyer.human();
|
||||
jv[sfCollateralAmount] = collateral.getJson(JsonOptions::Values::None);
|
||||
jv[sfPurchasePrice] = price.getJson(JsonOptions::Values::None);
|
||||
jv[sfInterestRate] = rate;
|
||||
jv[sfExpiration] = expiration;
|
||||
jv[sfMaturityDate] = maturity;
|
||||
jv[sfGracePeriod] = grace;
|
||||
return jv;
|
||||
}
|
||||
|
||||
static json::Value
|
||||
simple(json::StaticString const txType, jtx::Account const& account, uint256 const& repoID)
|
||||
{
|
||||
json::Value jv;
|
||||
jv[sfTransactionType] = txType;
|
||||
jv[sfAccount] = account.human();
|
||||
jv[sfRepoID] = to_string(repoID);
|
||||
return jv;
|
||||
}
|
||||
|
||||
static uint256
|
||||
repoID(jtx::Env& env, jtx::Account const& seller)
|
||||
{
|
||||
return keylet::repo(seller.id(), SeqProxy::rawSequence(env.seq(seller))).key;
|
||||
}
|
||||
|
||||
static bool
|
||||
exists(jtx::Env const& env, uint256 const& id)
|
||||
{
|
||||
return env.le(keylet::repo(id)) != nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
testEnabled(FeatureBitset features)
|
||||
{
|
||||
testcase("enabled");
|
||||
using namespace jtx;
|
||||
|
||||
{
|
||||
Env env{*this, features - featureRepo};
|
||||
Account const gw{"gw"}, seller{"seller"}, buyer{"buyer"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw, seller, buyer);
|
||||
env.close();
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
env(create(seller, buyer, XRP(1000), USD(900), 0, now + 100, now + 200, 60),
|
||||
Ter(temDISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
Env env{*this, features};
|
||||
Account const gw{"gw"}, seller{"seller"}, buyer{"buyer"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw, seller, buyer);
|
||||
env.close();
|
||||
env.trust(USD(100000), seller, buyer);
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
auto const ownersBefore = ownerCount(env, seller);
|
||||
auto const id = repoID(env, seller);
|
||||
env(create(seller, buyer, XRP(1000), USD(900), 0, now + 100, now + 200, 60));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(exists(env, id));
|
||||
BEAST_EXPECT(ownerCount(env, seller) == ownersBefore + 1);
|
||||
|
||||
auto const sle = env.le(keylet::repo(id));
|
||||
BEAST_EXPECT(sle && sle->getAccountID(sfAccount) == seller.id());
|
||||
BEAST_EXPECT(sle && sle->getAccountID(sfCounterparty) == buyer.id());
|
||||
// Pending until accepted.
|
||||
BEAST_EXPECT(sle && !sle->isFieldPresent(sfStartDate));
|
||||
}
|
||||
|
||||
void
|
||||
testCreateMalformed(FeatureBitset features)
|
||||
{
|
||||
testcase("create malformed");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const gw{"gw"}, seller{"seller"}, buyer{"buyer"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw, seller, buyer);
|
||||
env.close();
|
||||
env.trust(USD(100000), seller, buyer);
|
||||
env.close();
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
|
||||
// The counterparty may not be the account.
|
||||
env(create(seller, seller, XRP(1000), USD(900), 0, now + 100, now + 200, 60),
|
||||
Ter(temMALFORMED));
|
||||
|
||||
// Collateral and cash must differ: repaying in the locked asset would
|
||||
// let the seller settle with the very collateral that is immobilized.
|
||||
env(create(seller, buyer, XRP(1000), XRP(900), 0, now + 100, now + 200, 60),
|
||||
Ter(temBAD_AMOUNT));
|
||||
env(create(seller, buyer, XRP(1000), USD(900), 0, now + 100, now + 200, 60));
|
||||
env.close();
|
||||
|
||||
// Maturity must be after expiration.
|
||||
env(create(seller, buyer, XRP(1000), USD(900), 0, now + 300, now + 200, 60),
|
||||
Ter(temBAD_EXPIRATION));
|
||||
|
||||
// Amounts must be positive.
|
||||
env(create(seller, buyer, XRP(0), USD(900), 0, now + 100, now + 200, 60),
|
||||
Ter(temBAD_AMOUNT));
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
testAcceptAndClose(FeatureBitset features)
|
||||
{
|
||||
testcase("accept and close");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const gw{"gw"}, seller{"seller"}, buyer{"buyer"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw, seller, buyer);
|
||||
env.close();
|
||||
env.trust(USD(100000), seller, buyer);
|
||||
env.close();
|
||||
env(pay(gw, buyer, USD(5000)));
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
auto const id = repoID(env, seller);
|
||||
// Collateral XRP, cash USD, zero interest so the repurchase is exact.
|
||||
env(create(seller, buyer, XRP(1000), USD(900), 0, now + 100, now + 1000, 60));
|
||||
env.close();
|
||||
|
||||
auto const sellerUsdBefore = env.balance(seller, USD.issue());
|
||||
|
||||
// Only the pinned buyer may accept.
|
||||
env(simple(jss::RepoAccept, gw, id), Ter(tecNO_PERMISSION));
|
||||
|
||||
env(simple(jss::RepoAccept, buyer, id));
|
||||
env.close();
|
||||
|
||||
// The cash arrived and the repo is active.
|
||||
BEAST_EXPECT(env.balance(seller, USD.issue()) == sellerUsdBefore + USD(900));
|
||||
auto const sle = env.le(keylet::repo(id));
|
||||
BEAST_EXPECT(sle && sle->isFieldPresent(sfStartDate));
|
||||
|
||||
// A second accept fails.
|
||||
env(simple(jss::RepoAccept, buyer, id), Ter(tecREPO_ACTIVE));
|
||||
|
||||
// Only the seller may repurchase.
|
||||
env(simple(jss::RepoClose, buyer, id), Ter(tecNO_PERMISSION));
|
||||
|
||||
auto const buyerUsdBefore = env.balance(buyer, USD.issue());
|
||||
env(simple(jss::RepoClose, seller, id));
|
||||
env.close();
|
||||
|
||||
// Zero interest, so the buyer gets exactly the purchase price back and
|
||||
// the entry is gone.
|
||||
BEAST_EXPECT(env.balance(buyer, USD.issue()) == buyerUsdBefore + USD(900));
|
||||
BEAST_EXPECT(!exists(env, id));
|
||||
}
|
||||
|
||||
void
|
||||
testCancel(FeatureBitset features)
|
||||
{
|
||||
testcase("cancel");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const gw{"gw"}, seller{"seller"}, buyer{"buyer"}, stranger{"stranger"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw, seller, buyer, stranger);
|
||||
env.close();
|
||||
env.trust(USD(100000), seller, buyer);
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
auto const ownersBefore = ownerCount(env, seller);
|
||||
auto const id = repoID(env, seller);
|
||||
env(create(seller, buyer, XRP(1000), USD(900), 0, now + 100, now + 1000, 60));
|
||||
env.close();
|
||||
|
||||
// A stranger cannot cancel while the offer is live.
|
||||
env(simple(jss::RepoCancel, stranger, id), Ter(tecNO_PERMISSION));
|
||||
|
||||
// The seller can, at any time.
|
||||
env(simple(jss::RepoCancel, seller, id));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!exists(env, id));
|
||||
BEAST_EXPECT(ownerCount(env, seller) == ownersBefore);
|
||||
|
||||
// Once expired, anyone may clear it.
|
||||
auto const id2 = repoID(env, seller);
|
||||
auto const now2 = env.now().time_since_epoch().count();
|
||||
env(create(seller, buyer, XRP(1000), USD(900), 0, now2 + 10, now2 + 1000, 60));
|
||||
env.close();
|
||||
env.close(std::chrono::seconds(60));
|
||||
env(simple(jss::RepoCancel, stranger, id2));
|
||||
env.close();
|
||||
BEAST_EXPECT(!exists(env, id2));
|
||||
}
|
||||
|
||||
void
|
||||
testDefault(FeatureBitset features)
|
||||
{
|
||||
testcase("default");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const gw{"gw"}, seller{"seller"}, buyer{"buyer"}, keeper{"keeper"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw, seller, buyer, keeper);
|
||||
env.close();
|
||||
env.trust(USD(100000), seller, buyer);
|
||||
env.close();
|
||||
env(pay(gw, buyer, USD(5000)));
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
auto const id = repoID(env, seller);
|
||||
env(create(seller, buyer, XRP(1000), USD(900), 0, now + 100, now + 200, 60));
|
||||
env.close();
|
||||
env(simple(jss::RepoAccept, buyer, id));
|
||||
env.close();
|
||||
|
||||
// Not yet in default.
|
||||
env(simple(jss::RepoDefault, keeper, id), Ter(tecTOO_SOON));
|
||||
|
||||
auto const buyerXrpBefore = env.balance(buyer);
|
||||
env.close(std::chrono::seconds(400));
|
||||
|
||||
// Past maturity plus grace, anyone may close it out.
|
||||
env(simple(jss::RepoDefault, keeper, id));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!exists(env, id));
|
||||
// The buyer keeps the collateral.
|
||||
BEAST_EXPECT(env.balance(buyer) > buyerXrpBefore);
|
||||
}
|
||||
|
||||
void
|
||||
testInterest(FeatureBitset features)
|
||||
{
|
||||
testcase("interest accrues and is capped at maturity");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const gw{"gw"}, seller{"seller"}, buyer{"buyer"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw, seller, buyer);
|
||||
env.close();
|
||||
env.trust(USD(100000), seller, buyer);
|
||||
env.close();
|
||||
env(pay(gw, seller, USD(5000)));
|
||||
env(pay(gw, buyer, USD(5000)));
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
auto const id = repoID(env, seller);
|
||||
// kTenthBipsPerUnity is 100 percent annualized.
|
||||
env(create(
|
||||
seller,
|
||||
buyer,
|
||||
XRP(1000),
|
||||
USD(1000),
|
||||
kTenthBipsPerUnity.value(),
|
||||
now + 100,
|
||||
now + 400000,
|
||||
600));
|
||||
env.close();
|
||||
env(simple(jss::RepoAccept, buyer, id));
|
||||
env.close();
|
||||
|
||||
auto const start = (*env.le(keylet::repo(id)))[sfStartDate];
|
||||
auto const buyerBefore = env.balance(buyer, USD.issue());
|
||||
env.close(std::chrono::seconds(50000));
|
||||
|
||||
// The close time the transactor will use is the parent close time of the
|
||||
// ledger that applies it, which is the current open ledger's.
|
||||
auto const closeTime = env.current()->parentCloseTime().time_since_epoch().count();
|
||||
env(simple(jss::RepoClose, seller, id));
|
||||
env.close();
|
||||
|
||||
auto const elapsed = closeTime - start;
|
||||
BEAST_EXPECT(elapsed > 0);
|
||||
Number const expected = Number(1000) +
|
||||
tenthBipsOfValue(Number(1000) * Number(elapsed), kTenthBipsPerUnity) /
|
||||
Number(kSecondsInYear);
|
||||
auto const paid = env.balance(buyer, USD.issue()).value() - buyerBefore.value();
|
||||
|
||||
// Exactly the repurchase amount, so the rate's scale is pinned rather
|
||||
// than merely shown to be positive.
|
||||
BEAST_EXPECT(paid == STAmount(USD.issue(), expected));
|
||||
BEAST_EXPECT(!exists(env, id));
|
||||
}
|
||||
|
||||
// The rate carries the same ceiling LoanSet puts on sfInterestRate.
|
||||
void
|
||||
testInterestRateBound(FeatureBitset features)
|
||||
{
|
||||
testcase("the interest rate is bounded");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const gw{"gw"}, seller{"seller"}, buyer{"buyer"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw, seller, buyer);
|
||||
env.close();
|
||||
env.trust(USD(100000), seller, buyer);
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
env(create(
|
||||
seller,
|
||||
buyer,
|
||||
XRP(1000),
|
||||
USD(1000),
|
||||
lending::kMaxInterestRate.value() + 1,
|
||||
now + 100,
|
||||
now + 1000,
|
||||
600),
|
||||
Ter(temBAD_AMOUNT));
|
||||
env.close();
|
||||
|
||||
auto const id = repoID(env, seller);
|
||||
env(create(
|
||||
seller,
|
||||
buyer,
|
||||
XRP(1000),
|
||||
USD(1000),
|
||||
lending::kMaxInterestRate.value(),
|
||||
now + 100,
|
||||
now + 1000,
|
||||
600));
|
||||
env.close();
|
||||
BEAST_EXPECT(exists(env, id));
|
||||
}
|
||||
|
||||
// Collateral that is not XRP goes through the XLS-85 locking path, which
|
||||
// needs the issuer's opt-in and unfrozen, authorized parties on both sides.
|
||||
void
|
||||
testIouCollateral(FeatureBitset features)
|
||||
{
|
||||
testcase("IOU collateral");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const gw{"gw"}, cash{"cash"}, seller{"seller"}, buyer{"buyer"};
|
||||
auto const CLT = gw["CLT"];
|
||||
auto const USD = cash["USD"];
|
||||
env.fund(XRP(10000), gw, cash, seller, buyer);
|
||||
env.close();
|
||||
env.trust(CLT(100000), seller, buyer);
|
||||
env.trust(USD(100000), seller, buyer);
|
||||
env.close();
|
||||
env(pay(gw, seller, CLT(5000)));
|
||||
env(pay(cash, buyer, USD(5000)));
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
|
||||
// Without the issuer's locking opt-in the collateral cannot be locked.
|
||||
env(create(seller, buyer, CLT(1000), USD(900), 0, now + 100, now + 1000, 60),
|
||||
Ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
env(fset(gw, asfAllowTrustLineLocking));
|
||||
env.close();
|
||||
|
||||
auto const id = repoID(env, seller);
|
||||
env(create(seller, buyer, CLT(1000), USD(900), 0, now + 100, now + 1000, 60));
|
||||
env.close();
|
||||
BEAST_EXPECT(exists(env, id));
|
||||
|
||||
// The collateral left the seller's spendable balance.
|
||||
BEAST_EXPECT(env.balance(seller, CLT.issue()) == CLT(4000));
|
||||
|
||||
env(simple(jss::RepoAccept, buyer, id));
|
||||
env.close();
|
||||
|
||||
env(simple(jss::RepoClose, seller, id));
|
||||
env.close();
|
||||
|
||||
// Closing returns the collateral and the entry is gone.
|
||||
BEAST_EXPECT(!exists(env, id));
|
||||
BEAST_EXPECT(env.balance(seller, CLT.issue()) == CLT(5000));
|
||||
}
|
||||
|
||||
// On default the collateral must reach the buyer, so a frozen or
|
||||
// unauthorized buyer is rejected before anything is locked.
|
||||
void
|
||||
testIouCollateralFrozen(FeatureBitset features)
|
||||
{
|
||||
testcase("IOU collateral, frozen party");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const gw{"gw"}, cash{"cash"}, seller{"seller"}, buyer{"buyer"};
|
||||
auto const CLT = gw["CLT"];
|
||||
auto const USD = cash["USD"];
|
||||
env.fund(XRP(10000), gw, cash, seller, buyer);
|
||||
env.close();
|
||||
env(fset(gw, asfAllowTrustLineLocking));
|
||||
env.close();
|
||||
env.trust(CLT(100000), seller, buyer);
|
||||
env.trust(USD(100000), seller, buyer);
|
||||
env.close();
|
||||
env(pay(gw, seller, CLT(5000)));
|
||||
env(pay(cash, buyer, USD(5000)));
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
|
||||
// Freezing the buyer blocks the create, because the buyer is where the
|
||||
// collateral goes if the seller defaults.
|
||||
env(trust(gw, CLT(100000), buyer, tfSetFreeze));
|
||||
env.close();
|
||||
env(create(seller, buyer, CLT(1000), USD(900), 0, now + 100, now + 1000, 60),
|
||||
Ter(tecFROZEN));
|
||||
env.close();
|
||||
|
||||
env(trust(gw, CLT(100000), buyer, tfClearFreeze));
|
||||
env.close();
|
||||
|
||||
// And freezing the seller blocks it too.
|
||||
env(trust(gw, CLT(100000), seller, tfSetFreeze));
|
||||
env.close();
|
||||
env(create(seller, buyer, CLT(1000), USD(900), 0, now + 100, now + 1000, 60),
|
||||
Ter(tecFROZEN));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// MPT collateral locks through the issuance's locked-amount accounting
|
||||
// rather than a trustline, and needs the issuance to permit escrow.
|
||||
void
|
||||
testMptCollateral(FeatureBitset features)
|
||||
{
|
||||
testcase("MPT collateral");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const gw{"gw"}, cash{"cash"}, seller{"seller"}, buyer{"buyer"};
|
||||
auto const USD = cash["USD"];
|
||||
env.fund(XRP(10000), gw, cash, seller, buyer);
|
||||
env.close();
|
||||
env.trust(USD(100000), seller, buyer);
|
||||
env.close();
|
||||
env(pay(cash, buyer, USD(5000)));
|
||||
env.close();
|
||||
|
||||
// An issuance that does not permit escrow cannot be locked.
|
||||
{
|
||||
MPTTester noLock(env, gw, {.holders = {seller, buyer}, .fund = false});
|
||||
noLock.create({.flags = tfMPTCanTransfer});
|
||||
noLock.authorize({.account = seller});
|
||||
noLock.authorize({.account = buyer});
|
||||
auto const mpt = noLock["MPT"];
|
||||
env(pay(gw, seller, mpt(10000)));
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
env(create(seller, buyer, mpt(1000), USD(900), 0, now + 100, now + 1000, 60),
|
||||
Ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
}
|
||||
|
||||
MPTTester mptGw(env, gw, {.holders = {seller, buyer}, .fund = false});
|
||||
mptGw.create({.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
||||
mptGw.authorize({.account = seller});
|
||||
mptGw.authorize({.account = buyer});
|
||||
auto const mpt = mptGw["MPT"];
|
||||
env(pay(gw, seller, mpt(10000)));
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
auto const id = repoID(env, seller);
|
||||
env(create(seller, buyer, mpt(1000), USD(900), 0, now + 100, now + 1000, 60));
|
||||
env.close();
|
||||
BEAST_EXPECT(exists(env, id));
|
||||
|
||||
env(simple(jss::RepoAccept, buyer, id));
|
||||
env.close();
|
||||
|
||||
// Default hands the collateral to the buyer, exercising the MPT
|
||||
// unlock-to-a-third-party path.
|
||||
env.close(std::chrono::seconds(2000));
|
||||
env(simple(jss::RepoDefault, buyer, id));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!exists(env, id));
|
||||
}
|
||||
|
||||
// The spec requires an open repo to block account deletion, which falls out
|
||||
// of ltREPO being absent from nonObligationDeleter. Both parties carry the
|
||||
// entry in their owner directory, so both are blocked.
|
||||
void
|
||||
testAccountDeleteBlocked(FeatureBitset features)
|
||||
{
|
||||
testcase("an open repo blocks account deletion");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const gw{"gw"}, seller{"seller"}, buyer{"buyer"}, sink{"sink"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw, seller, buyer, sink);
|
||||
env.close();
|
||||
env.trust(USD(100000), seller, buyer);
|
||||
env.close();
|
||||
env(pay(gw, buyer, USD(5000)));
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
auto const id = repoID(env, seller);
|
||||
env(create(seller, buyer, XRP(1000), USD(1000), 0, now + 100, now + 100000, 600));
|
||||
env.close();
|
||||
env(simple(jss::RepoAccept, buyer, id));
|
||||
env.close();
|
||||
|
||||
// Both parties carry the entry, so both are refused.
|
||||
incLgrSeqForAccDel(env, seller);
|
||||
env(acctdelete(seller, sink),
|
||||
Fee(drops(env.current()->fees().increment)),
|
||||
Ter(tecHAS_OBLIGATIONS));
|
||||
env.close();
|
||||
env(acctdelete(buyer, sink),
|
||||
Fee(drops(env.current()->fees().increment)),
|
||||
Ter(tecHAS_OBLIGATIONS));
|
||||
env.close();
|
||||
|
||||
// With the repo gone and the zero-balance trust line removed, the same
|
||||
// account deletes, so the refusal above was the repo and nothing else.
|
||||
env(simple(jss::RepoClose, seller, id));
|
||||
env.close();
|
||||
BEAST_EXPECT(!exists(env, id));
|
||||
BEAST_EXPECT(env.balance(seller, USD.issue()) == USD(0));
|
||||
|
||||
env(trust(seller, USD(0)));
|
||||
env.close();
|
||||
env(acctdelete(seller, sink), Fee(drops(env.current()->fees().increment)));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(keylet::account(seller.id())));
|
||||
}
|
||||
|
||||
// account_objects and ledger_entry both resolve the entry under the name the
|
||||
// LEDGER_ENTRY declaration gives it.
|
||||
void
|
||||
testRpc(FeatureBitset features)
|
||||
{
|
||||
testcase("account_objects and ledger_entry");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const gw{"gw"}, seller{"seller"}, buyer{"buyer"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw, seller, buyer);
|
||||
env.close();
|
||||
env.trust(USD(100000), seller, buyer);
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
auto const id = repoID(env, seller);
|
||||
env(create(seller, buyer, XRP(1000), USD(1000), 500, now + 100, now + 100000, 600));
|
||||
env.close();
|
||||
|
||||
auto const objects = [&](jtx::Account const& who) {
|
||||
json::Value params;
|
||||
params[jss::account] = who.human();
|
||||
params[jss::type] = "repo";
|
||||
return env.rpc("json", "account_objects", to_string(params))[jss::result];
|
||||
};
|
||||
|
||||
for (auto const& who : {seller, buyer})
|
||||
{
|
||||
auto const jv = objects(who);
|
||||
BEAST_EXPECT(jv[jss::account_objects].size() == 1);
|
||||
auto const& object = jv[jss::account_objects][0u];
|
||||
BEAST_EXPECT(object["LedgerEntryType"].asString() == "Repo");
|
||||
BEAST_EXPECT(object[jss::index].asString() == to_string(id));
|
||||
}
|
||||
|
||||
{
|
||||
json::Value params;
|
||||
params[jss::repo] = json::ValueType::Object;
|
||||
params[jss::repo][jss::account] = seller.human();
|
||||
params[jss::repo][jss::seq] = env.seq(seller) - 1;
|
||||
auto const jv = env.rpc("json", "ledger_entry", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(jv[jss::index].asString() == to_string(id));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const all = jtx::testableAmendments();
|
||||
testEnabled(all);
|
||||
testCreateMalformed(all);
|
||||
testAcceptAndClose(all);
|
||||
testCancel(all);
|
||||
testDefault(all);
|
||||
testInterest(all);
|
||||
testInterestRateBound(all);
|
||||
testIouCollateral(all);
|
||||
testIouCollateralFrozen(all);
|
||||
testMptCollateral(all);
|
||||
testAccountDeleteBlocked(all);
|
||||
testRpc(all);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Repo, app, xrpl);
|
||||
|
||||
} // namespace xrpl::test
|
||||
239
src/test/app/invariants/InvariantsRepo_test.cpp
Normal file
239
src/test/app/invariants/InvariantsRepo_test.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#include <test/app/invariants/InvariantsBase.h>
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/trust.h>
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
class InvariantsRepo_test : public InvariantsBase
|
||||
{
|
||||
// A repo's terms are struck once. Each case below restates one of them
|
||||
// after the fact, which the invariant must reject.
|
||||
void
|
||||
testRepoTermsAreImmutable()
|
||||
{
|
||||
testcase("repo terms are immutable");
|
||||
using namespace jtx;
|
||||
|
||||
uint256 repoKey;
|
||||
Account const gw{"gw"};
|
||||
|
||||
auto const createRepo = [&](Account const& a1, Account const& a2, Env& env) {
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw);
|
||||
env.close();
|
||||
env.trust(USD(100000), a1, a2);
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
repoKey = keylet::repo(a1.id(), SeqProxy::rawSequence(env.seq(a1))).key;
|
||||
|
||||
json::Value jv;
|
||||
jv[sfTransactionType] = jss::RepoCreate;
|
||||
jv[sfAccount] = a1.human();
|
||||
jv[sfCounterparty] = a2.human();
|
||||
jv[sfCollateralAmount] = XRP(10).value().getJson(JsonOptions::Values::None);
|
||||
jv[sfPurchasePrice] = USD(9).value().getJson(JsonOptions::Values::None);
|
||||
jv[sfInterestRate] = 0;
|
||||
jv[sfExpiration] = now + 100;
|
||||
jv[sfMaturityDate] = now + 1000;
|
||||
jv[sfGracePeriod] = 60;
|
||||
env(jv);
|
||||
env.close();
|
||||
return true;
|
||||
};
|
||||
|
||||
struct Mod
|
||||
{
|
||||
std::string expected;
|
||||
std::function<void(SLE::pointer&)> func;
|
||||
};
|
||||
|
||||
std::vector<Mod> const mods{
|
||||
{"a repo's seller is its counterparty",
|
||||
[](SLE::pointer& sle) { (*sle)[sfCounterparty] = (*sle)[sfAccount]; }},
|
||||
{"a repo's collateral or price is not positive",
|
||||
[](SLE::pointer& sle) { (*sle)[sfCollateralAmount] = STAmount{XRPAmount{0}}; }},
|
||||
{"a repo matures before it expires",
|
||||
[](SLE::pointer& sle) { (*sle)[sfMaturityDate] = (*sle)[sfExpiration] - 1; }},
|
||||
{"a repo's terms changed after creation",
|
||||
[](SLE::pointer& sle) { (*sle)[sfInterestRate] = (*sle)[sfInterestRate] + 1; }},
|
||||
{"a repo's terms changed after creation",
|
||||
[](SLE::pointer& sle) { (*sle)[sfCollateralAmount] = STAmount{XRPAmount{1}}; }},
|
||||
};
|
||||
|
||||
for (auto const& mod : mods)
|
||||
{
|
||||
doInvariantCheck(
|
||||
{{mod.expected}},
|
||||
[&](Account const&, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(keylet::repo(repoKey));
|
||||
if (!sle)
|
||||
return false;
|
||||
mod.func(sle);
|
||||
ac.view().update(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
createRepo);
|
||||
}
|
||||
}
|
||||
|
||||
// A repo moves from pending to active and never back, and the start date
|
||||
// it was stamped with is the one interest is computed from.
|
||||
void
|
||||
testRepoOnlyMovesForward()
|
||||
{
|
||||
testcase("repo only moves forward");
|
||||
using namespace jtx;
|
||||
|
||||
uint256 repoKey;
|
||||
Account const gw{"gw"};
|
||||
|
||||
auto const createActiveRepo = [&](Account const& a1, Account const& a2, Env& env) {
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw);
|
||||
env.close();
|
||||
env.trust(USD(100000), a1, a2);
|
||||
env.close();
|
||||
env(pay(gw, a2, USD(5000)));
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
repoKey = keylet::repo(a1.id(), SeqProxy::rawSequence(env.seq(a1))).key;
|
||||
|
||||
json::Value jv;
|
||||
jv[sfTransactionType] = jss::RepoCreate;
|
||||
jv[sfAccount] = a1.human();
|
||||
jv[sfCounterparty] = a2.human();
|
||||
jv[sfCollateralAmount] = XRP(10).value().getJson(JsonOptions::Values::None);
|
||||
jv[sfPurchasePrice] = USD(9).value().getJson(JsonOptions::Values::None);
|
||||
jv[sfInterestRate] = 0;
|
||||
jv[sfExpiration] = now + 100;
|
||||
jv[sfMaturityDate] = now + 1000;
|
||||
jv[sfGracePeriod] = 60;
|
||||
env(jv);
|
||||
env.close();
|
||||
|
||||
json::Value accept;
|
||||
accept[sfTransactionType] = jss::RepoAccept;
|
||||
accept[sfAccount] = a2.human();
|
||||
accept[sfRepoID] = to_string(repoKey);
|
||||
env(accept);
|
||||
env.close();
|
||||
return true;
|
||||
};
|
||||
|
||||
doInvariantCheck(
|
||||
{{"an active repo reverted to pending"}},
|
||||
[&](Account const&, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(keylet::repo(repoKey));
|
||||
if (!sle || !sle->isFieldPresent(sfStartDate))
|
||||
return false;
|
||||
sle->makeFieldAbsent(sfStartDate);
|
||||
ac.view().update(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
createActiveRepo);
|
||||
|
||||
doInvariantCheck(
|
||||
{{"a repo's start date changed"}},
|
||||
[&](Account const&, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(keylet::repo(repoKey));
|
||||
if (!sle || !sle->isFieldPresent(sfStartDate))
|
||||
return false;
|
||||
(*sle)[sfStartDate] = (*sle)[sfStartDate] + 1;
|
||||
ac.view().update(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
createActiveRepo);
|
||||
}
|
||||
|
||||
// A repo may only be removed by cancel, close or default.
|
||||
void
|
||||
testRepoDeletedByWrongTransaction()
|
||||
{
|
||||
testcase("repo deleted by the wrong transaction");
|
||||
using namespace jtx;
|
||||
|
||||
uint256 repoKey;
|
||||
Account const gw{"gw"};
|
||||
|
||||
auto const createRepo = [&](Account const& a1, Account const& a2, Env& env) {
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw);
|
||||
env.close();
|
||||
env.trust(USD(100000), a1, a2);
|
||||
env.close();
|
||||
|
||||
auto const now = env.now().time_since_epoch().count();
|
||||
repoKey = keylet::repo(a1.id(), SeqProxy::rawSequence(env.seq(a1))).key;
|
||||
|
||||
json::Value jv;
|
||||
jv[sfTransactionType] = jss::RepoCreate;
|
||||
jv[sfAccount] = a1.human();
|
||||
jv[sfCounterparty] = a2.human();
|
||||
jv[sfCollateralAmount] = XRP(10).value().getJson(JsonOptions::Values::None);
|
||||
jv[sfPurchasePrice] = USD(9).value().getJson(JsonOptions::Values::None);
|
||||
jv[sfInterestRate] = 0;
|
||||
jv[sfExpiration] = now + 100;
|
||||
jv[sfMaturityDate] = now + 1000;
|
||||
jv[sfGracePeriod] = 60;
|
||||
env(jv);
|
||||
env.close();
|
||||
return true;
|
||||
};
|
||||
|
||||
doInvariantCheck(
|
||||
{{"a repo was deleted by transaction type"}},
|
||||
[&](Account const&, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(keylet::repo(repoKey));
|
||||
if (!sle)
|
||||
return false;
|
||||
ac.view().erase(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
createRepo);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testRepoTermsAreImmutable();
|
||||
testRepoOnlyMovesForward();
|
||||
testRepoDeletedByWrongTransaction();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(InvariantsRepo, app, xrpl);
|
||||
|
||||
} // namespace xrpl::test
|
||||
488
src/tests/libxrpl/protocol_autogen/ledger_entries/RepoTests.cpp
Normal file
488
src/tests/libxrpl/protocol_autogen/ledger_entries/RepoTests.cpp
Normal file
@@ -0,0 +1,488 @@
|
||||
// Auto-generated unit tests for ledger entry Repo
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol_autogen/ledger_entries/Repo.h>
|
||||
#include <xrpl/protocol_autogen/ledger_entries/Ticket.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::ledger_entries {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed for both the
|
||||
// builder's STObject and the wrapper's SLE.
|
||||
TEST(RepoTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
uint256 const index{1u};
|
||||
|
||||
auto const accountValue = canonical_ACCOUNT();
|
||||
auto const counterpartyValue = canonical_ACCOUNT();
|
||||
auto const collateralAmountValue = canonical_AMOUNT();
|
||||
auto const purchasePriceValue = canonical_AMOUNT();
|
||||
auto const interestRateValue = canonical_UINT32();
|
||||
auto const expirationValue = canonical_UINT32();
|
||||
auto const startDateValue = canonical_UINT32();
|
||||
auto const maturityDateValue = canonical_UINT32();
|
||||
auto const gracePeriodValue = canonical_UINT32();
|
||||
auto const transferRateValue = canonical_UINT32();
|
||||
auto const dataValue = canonical_VL();
|
||||
auto const ownerNodeValue = canonical_UINT64();
|
||||
auto const destinationNodeValue = canonical_UINT64();
|
||||
auto const issuerNodeValue = canonical_UINT64();
|
||||
auto const previousTxnIDValue = canonical_UINT256();
|
||||
auto const previousTxnLgrSeqValue = canonical_UINT32();
|
||||
|
||||
RepoBuilder builder{
|
||||
accountValue,
|
||||
counterpartyValue,
|
||||
collateralAmountValue,
|
||||
purchasePriceValue,
|
||||
interestRateValue,
|
||||
expirationValue,
|
||||
maturityDateValue,
|
||||
gracePeriodValue,
|
||||
ownerNodeValue,
|
||||
destinationNodeValue,
|
||||
previousTxnIDValue,
|
||||
previousTxnLgrSeqValue
|
||||
};
|
||||
|
||||
builder.setStartDate(startDateValue);
|
||||
builder.setTransferRate(transferRateValue);
|
||||
builder.setData(dataValue);
|
||||
builder.setIssuerNode(issuerNodeValue);
|
||||
|
||||
builder.setLedgerIndex(index);
|
||||
builder.setFlags(0x1u);
|
||||
|
||||
EXPECT_TRUE(builder.validate());
|
||||
|
||||
auto const entry = builder.build(index);
|
||||
|
||||
EXPECT_TRUE(entry.validate());
|
||||
|
||||
{
|
||||
auto const& expected = accountValue;
|
||||
auto const actual = entry.getAccount();
|
||||
expectEqualField(expected, actual, "sfAccount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = counterpartyValue;
|
||||
auto const actual = entry.getCounterparty();
|
||||
expectEqualField(expected, actual, "sfCounterparty");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = collateralAmountValue;
|
||||
auto const actual = entry.getCollateralAmount();
|
||||
expectEqualField(expected, actual, "sfCollateralAmount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = purchasePriceValue;
|
||||
auto const actual = entry.getPurchasePrice();
|
||||
expectEqualField(expected, actual, "sfPurchasePrice");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = interestRateValue;
|
||||
auto const actual = entry.getInterestRate();
|
||||
expectEqualField(expected, actual, "sfInterestRate");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = expirationValue;
|
||||
auto const actual = entry.getExpiration();
|
||||
expectEqualField(expected, actual, "sfExpiration");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = maturityDateValue;
|
||||
auto const actual = entry.getMaturityDate();
|
||||
expectEqualField(expected, actual, "sfMaturityDate");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gracePeriodValue;
|
||||
auto const actual = entry.getGracePeriod();
|
||||
expectEqualField(expected, actual, "sfGracePeriod");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = ownerNodeValue;
|
||||
auto const actual = entry.getOwnerNode();
|
||||
expectEqualField(expected, actual, "sfOwnerNode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = destinationNodeValue;
|
||||
auto const actual = entry.getDestinationNode();
|
||||
expectEqualField(expected, actual, "sfDestinationNode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnIDValue;
|
||||
auto const actual = entry.getPreviousTxnID();
|
||||
expectEqualField(expected, actual, "sfPreviousTxnID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnLgrSeqValue;
|
||||
auto const actual = entry.getPreviousTxnLgrSeq();
|
||||
expectEqualField(expected, actual, "sfPreviousTxnLgrSeq");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = startDateValue;
|
||||
auto const actualOpt = entry.getStartDate();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfStartDate");
|
||||
EXPECT_TRUE(entry.hasStartDate());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = transferRateValue;
|
||||
auto const actualOpt = entry.getTransferRate();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfTransferRate");
|
||||
EXPECT_TRUE(entry.hasTransferRate());
|
||||
}
|
||||
|
||||
{
|
||||
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 = issuerNodeValue;
|
||||
auto const actualOpt = entry.getIssuerNode();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfIssuerNode");
|
||||
EXPECT_TRUE(entry.hasIssuerNode());
|
||||
}
|
||||
|
||||
EXPECT_TRUE(entry.hasLedgerIndex());
|
||||
auto const ledgerIndex = entry.getLedgerIndex();
|
||||
ASSERT_TRUE(ledgerIndex.has_value());
|
||||
EXPECT_EQ(*ledgerIndex, index);
|
||||
EXPECT_EQ(entry.getKey(), index);
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an SLE, set fields directly on it, construct a builder
|
||||
// from that SLE, build a new wrapper, and verify all fields (and validate()).
|
||||
TEST(RepoTests, BuilderFromSleRoundTrip)
|
||||
{
|
||||
uint256 const index{2u};
|
||||
|
||||
auto const accountValue = canonical_ACCOUNT();
|
||||
auto const counterpartyValue = canonical_ACCOUNT();
|
||||
auto const collateralAmountValue = canonical_AMOUNT();
|
||||
auto const purchasePriceValue = canonical_AMOUNT();
|
||||
auto const interestRateValue = canonical_UINT32();
|
||||
auto const expirationValue = canonical_UINT32();
|
||||
auto const startDateValue = canonical_UINT32();
|
||||
auto const maturityDateValue = canonical_UINT32();
|
||||
auto const gracePeriodValue = canonical_UINT32();
|
||||
auto const transferRateValue = canonical_UINT32();
|
||||
auto const dataValue = canonical_VL();
|
||||
auto const ownerNodeValue = canonical_UINT64();
|
||||
auto const destinationNodeValue = canonical_UINT64();
|
||||
auto const issuerNodeValue = canonical_UINT64();
|
||||
auto const previousTxnIDValue = canonical_UINT256();
|
||||
auto const previousTxnLgrSeqValue = canonical_UINT32();
|
||||
|
||||
auto sle = std::make_shared<SLE>(Repo::entryType, index);
|
||||
|
||||
sle->at(sfAccount) = accountValue;
|
||||
sle->at(sfCounterparty) = counterpartyValue;
|
||||
sle->at(sfCollateralAmount) = collateralAmountValue;
|
||||
sle->at(sfPurchasePrice) = purchasePriceValue;
|
||||
sle->at(sfInterestRate) = interestRateValue;
|
||||
sle->at(sfExpiration) = expirationValue;
|
||||
sle->at(sfStartDate) = startDateValue;
|
||||
sle->at(sfMaturityDate) = maturityDateValue;
|
||||
sle->at(sfGracePeriod) = gracePeriodValue;
|
||||
sle->at(sfTransferRate) = transferRateValue;
|
||||
sle->at(sfData) = dataValue;
|
||||
sle->at(sfOwnerNode) = ownerNodeValue;
|
||||
sle->at(sfDestinationNode) = destinationNodeValue;
|
||||
sle->at(sfIssuerNode) = issuerNodeValue;
|
||||
sle->at(sfPreviousTxnID) = previousTxnIDValue;
|
||||
sle->at(sfPreviousTxnLgrSeq) = previousTxnLgrSeqValue;
|
||||
|
||||
RepoBuilder builderFromSle{sle};
|
||||
EXPECT_TRUE(builderFromSle.validate());
|
||||
|
||||
auto const entryFromBuilder = builderFromSle.build(index);
|
||||
|
||||
Repo entryFromSle{sle};
|
||||
EXPECT_TRUE(entryFromBuilder.validate());
|
||||
EXPECT_TRUE(entryFromSle.validate());
|
||||
|
||||
{
|
||||
auto const& expected = accountValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getAccount();
|
||||
auto const fromBuilder = entryFromBuilder.getAccount();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfAccount");
|
||||
expectEqualField(expected, fromBuilder, "sfAccount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = counterpartyValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getCounterparty();
|
||||
auto const fromBuilder = entryFromBuilder.getCounterparty();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfCounterparty");
|
||||
expectEqualField(expected, fromBuilder, "sfCounterparty");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = collateralAmountValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getCollateralAmount();
|
||||
auto const fromBuilder = entryFromBuilder.getCollateralAmount();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfCollateralAmount");
|
||||
expectEqualField(expected, fromBuilder, "sfCollateralAmount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = purchasePriceValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getPurchasePrice();
|
||||
auto const fromBuilder = entryFromBuilder.getPurchasePrice();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfPurchasePrice");
|
||||
expectEqualField(expected, fromBuilder, "sfPurchasePrice");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = interestRateValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getInterestRate();
|
||||
auto const fromBuilder = entryFromBuilder.getInterestRate();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfInterestRate");
|
||||
expectEqualField(expected, fromBuilder, "sfInterestRate");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = expirationValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getExpiration();
|
||||
auto const fromBuilder = entryFromBuilder.getExpiration();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfExpiration");
|
||||
expectEqualField(expected, fromBuilder, "sfExpiration");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = maturityDateValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getMaturityDate();
|
||||
auto const fromBuilder = entryFromBuilder.getMaturityDate();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfMaturityDate");
|
||||
expectEqualField(expected, fromBuilder, "sfMaturityDate");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gracePeriodValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getGracePeriod();
|
||||
auto const fromBuilder = entryFromBuilder.getGracePeriod();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfGracePeriod");
|
||||
expectEqualField(expected, fromBuilder, "sfGracePeriod");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = ownerNodeValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getOwnerNode();
|
||||
auto const fromBuilder = entryFromBuilder.getOwnerNode();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfOwnerNode");
|
||||
expectEqualField(expected, fromBuilder, "sfOwnerNode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = destinationNodeValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getDestinationNode();
|
||||
auto const fromBuilder = entryFromBuilder.getDestinationNode();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfDestinationNode");
|
||||
expectEqualField(expected, fromBuilder, "sfDestinationNode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnIDValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getPreviousTxnID();
|
||||
auto const fromBuilder = entryFromBuilder.getPreviousTxnID();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfPreviousTxnID");
|
||||
expectEqualField(expected, fromBuilder, "sfPreviousTxnID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnLgrSeqValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getPreviousTxnLgrSeq();
|
||||
auto const fromBuilder = entryFromBuilder.getPreviousTxnLgrSeq();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfPreviousTxnLgrSeq");
|
||||
expectEqualField(expected, fromBuilder, "sfPreviousTxnLgrSeq");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = startDateValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getStartDate();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getStartDate();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfStartDate");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfStartDate");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = transferRateValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getTransferRate();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getTransferRate();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfTransferRate");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfTransferRate");
|
||||
}
|
||||
|
||||
{
|
||||
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 = issuerNodeValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getIssuerNode();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getIssuerNode();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfIssuerNode");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfIssuerNode");
|
||||
}
|
||||
|
||||
EXPECT_EQ(entryFromSle.getKey(), index);
|
||||
EXPECT_EQ(entryFromBuilder.getKey(), index);
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong ledger entry type.
|
||||
TEST(RepoTests, WrapperThrowsOnWrongEntryType)
|
||||
{
|
||||
uint256 const index{3u};
|
||||
|
||||
// Build a valid ledger entry of a different type
|
||||
// Ticket requires: Account, OwnerNode, TicketSequence, PreviousTxnID, PreviousTxnLgrSeq
|
||||
// Check requires: Account, Destination, SendMax, Sequence, OwnerNode, DestinationNode, PreviousTxnID, PreviousTxnLgrSeq
|
||||
TicketBuilder wrongBuilder{
|
||||
canonical_ACCOUNT(),
|
||||
canonical_UINT64(),
|
||||
canonical_UINT32(),
|
||||
canonical_UINT256(),
|
||||
canonical_UINT32()};
|
||||
auto wrongEntry = wrongBuilder.build(index);
|
||||
|
||||
EXPECT_THROW(Repo{wrongEntry.getSle()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong ledger entry type.
|
||||
TEST(RepoTests, BuilderThrowsOnWrongEntryType)
|
||||
{
|
||||
uint256 const index{4u};
|
||||
|
||||
// Build a valid ledger entry of a different type
|
||||
TicketBuilder wrongBuilder{
|
||||
canonical_ACCOUNT(),
|
||||
canonical_UINT64(),
|
||||
canonical_UINT32(),
|
||||
canonical_UINT256(),
|
||||
canonical_UINT32()};
|
||||
auto wrongEntry = wrongBuilder.build(index);
|
||||
|
||||
EXPECT_THROW(RepoBuilder{wrongEntry.getSle()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(RepoTests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
uint256 const index{3u};
|
||||
|
||||
auto const accountValue = canonical_ACCOUNT();
|
||||
auto const counterpartyValue = canonical_ACCOUNT();
|
||||
auto const collateralAmountValue = canonical_AMOUNT();
|
||||
auto const purchasePriceValue = canonical_AMOUNT();
|
||||
auto const interestRateValue = canonical_UINT32();
|
||||
auto const expirationValue = canonical_UINT32();
|
||||
auto const maturityDateValue = canonical_UINT32();
|
||||
auto const gracePeriodValue = canonical_UINT32();
|
||||
auto const ownerNodeValue = canonical_UINT64();
|
||||
auto const destinationNodeValue = canonical_UINT64();
|
||||
auto const previousTxnIDValue = canonical_UINT256();
|
||||
auto const previousTxnLgrSeqValue = canonical_UINT32();
|
||||
|
||||
RepoBuilder builder{
|
||||
accountValue,
|
||||
counterpartyValue,
|
||||
collateralAmountValue,
|
||||
purchasePriceValue,
|
||||
interestRateValue,
|
||||
expirationValue,
|
||||
maturityDateValue,
|
||||
gracePeriodValue,
|
||||
ownerNodeValue,
|
||||
destinationNodeValue,
|
||||
previousTxnIDValue,
|
||||
previousTxnLgrSeqValue
|
||||
};
|
||||
|
||||
auto const entry = builder.build(index);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(entry.hasStartDate());
|
||||
EXPECT_FALSE(entry.getStartDate().has_value());
|
||||
EXPECT_FALSE(entry.hasTransferRate());
|
||||
EXPECT_FALSE(entry.getTransferRate().has_value());
|
||||
EXPECT_FALSE(entry.hasData());
|
||||
EXPECT_FALSE(entry.getData().has_value());
|
||||
EXPECT_FALSE(entry.hasIssuerNode());
|
||||
EXPECT_FALSE(entry.getIssuerNode().has_value());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Auto-generated unit tests for transaction RepoAccept
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/RepoAccept.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsRepoAcceptTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testRepoAccept"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const repoIDValue = canonical_UINT256();
|
||||
|
||||
RepoAcceptBuilder builder{
|
||||
accountValue,
|
||||
repoIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = repoIDValue;
|
||||
auto const actual = tx.getRepoID();
|
||||
expectEqualField(expected, actual, "sfRepoID");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsRepoAcceptTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testRepoAcceptFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const repoIDValue = canonical_UINT256();
|
||||
|
||||
// Build an initial transaction
|
||||
RepoAcceptBuilder initialBuilder{
|
||||
accountValue,
|
||||
repoIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
RepoAcceptBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = repoIDValue;
|
||||
auto const actual = rebuiltTx.getRepoID();
|
||||
expectEqualField(expected, actual, "sfRepoID");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsRepoAcceptTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(RepoAccept{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsRepoAcceptTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(RepoAcceptBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Auto-generated unit tests for transaction RepoCancel
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/RepoCancel.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsRepoCancelTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testRepoCancel"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const repoIDValue = canonical_UINT256();
|
||||
|
||||
RepoCancelBuilder builder{
|
||||
accountValue,
|
||||
repoIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = repoIDValue;
|
||||
auto const actual = tx.getRepoID();
|
||||
expectEqualField(expected, actual, "sfRepoID");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsRepoCancelTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testRepoCancelFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const repoIDValue = canonical_UINT256();
|
||||
|
||||
// Build an initial transaction
|
||||
RepoCancelBuilder initialBuilder{
|
||||
accountValue,
|
||||
repoIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
RepoCancelBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = repoIDValue;
|
||||
auto const actual = rebuiltTx.getRepoID();
|
||||
expectEqualField(expected, actual, "sfRepoID");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsRepoCancelTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(RepoCancel{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsRepoCancelTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(RepoCancelBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Auto-generated unit tests for transaction RepoClose
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/RepoClose.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsRepoCloseTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testRepoClose"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const repoIDValue = canonical_UINT256();
|
||||
|
||||
RepoCloseBuilder builder{
|
||||
accountValue,
|
||||
repoIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = repoIDValue;
|
||||
auto const actual = tx.getRepoID();
|
||||
expectEqualField(expected, actual, "sfRepoID");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsRepoCloseTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testRepoCloseFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const repoIDValue = canonical_UINT256();
|
||||
|
||||
// Build an initial transaction
|
||||
RepoCloseBuilder initialBuilder{
|
||||
accountValue,
|
||||
repoIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
RepoCloseBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = repoIDValue;
|
||||
auto const actual = rebuiltTx.getRepoID();
|
||||
expectEqualField(expected, actual, "sfRepoID");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsRepoCloseTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(RepoClose{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsRepoCloseTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(RepoCloseBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
// Auto-generated unit tests for transaction RepoCreate
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/RepoCreate.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsRepoCreateTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testRepoCreate"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const counterpartyValue = canonical_ACCOUNT();
|
||||
auto const collateralAmountValue = canonical_AMOUNT();
|
||||
auto const purchasePriceValue = canonical_AMOUNT();
|
||||
auto const interestRateValue = canonical_UINT32();
|
||||
auto const expirationValue = canonical_UINT32();
|
||||
auto const maturityDateValue = canonical_UINT32();
|
||||
auto const gracePeriodValue = canonical_UINT32();
|
||||
auto const dataValue = canonical_VL();
|
||||
|
||||
RepoCreateBuilder builder{
|
||||
accountValue,
|
||||
counterpartyValue,
|
||||
collateralAmountValue,
|
||||
purchasePriceValue,
|
||||
interestRateValue,
|
||||
expirationValue,
|
||||
maturityDateValue,
|
||||
gracePeriodValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
builder.setData(dataValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = counterpartyValue;
|
||||
auto const actual = tx.getCounterparty();
|
||||
expectEqualField(expected, actual, "sfCounterparty");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = collateralAmountValue;
|
||||
auto const actual = tx.getCollateralAmount();
|
||||
expectEqualField(expected, actual, "sfCollateralAmount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = purchasePriceValue;
|
||||
auto const actual = tx.getPurchasePrice();
|
||||
expectEqualField(expected, actual, "sfPurchasePrice");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = interestRateValue;
|
||||
auto const actual = tx.getInterestRate();
|
||||
expectEqualField(expected, actual, "sfInterestRate");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = expirationValue;
|
||||
auto const actual = tx.getExpiration();
|
||||
expectEqualField(expected, actual, "sfExpiration");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = maturityDateValue;
|
||||
auto const actual = tx.getMaturityDate();
|
||||
expectEqualField(expected, actual, "sfMaturityDate");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gracePeriodValue;
|
||||
auto const actual = tx.getGracePeriod();
|
||||
expectEqualField(expected, actual, "sfGracePeriod");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsRepoCreateTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testRepoCreateFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const counterpartyValue = canonical_ACCOUNT();
|
||||
auto const collateralAmountValue = canonical_AMOUNT();
|
||||
auto const purchasePriceValue = canonical_AMOUNT();
|
||||
auto const interestRateValue = canonical_UINT32();
|
||||
auto const expirationValue = canonical_UINT32();
|
||||
auto const maturityDateValue = canonical_UINT32();
|
||||
auto const gracePeriodValue = canonical_UINT32();
|
||||
auto const dataValue = canonical_VL();
|
||||
|
||||
// Build an initial transaction
|
||||
RepoCreateBuilder initialBuilder{
|
||||
accountValue,
|
||||
counterpartyValue,
|
||||
collateralAmountValue,
|
||||
purchasePriceValue,
|
||||
interestRateValue,
|
||||
expirationValue,
|
||||
maturityDateValue,
|
||||
gracePeriodValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
initialBuilder.setData(dataValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
RepoCreateBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = counterpartyValue;
|
||||
auto const actual = rebuiltTx.getCounterparty();
|
||||
expectEqualField(expected, actual, "sfCounterparty");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = collateralAmountValue;
|
||||
auto const actual = rebuiltTx.getCollateralAmount();
|
||||
expectEqualField(expected, actual, "sfCollateralAmount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = purchasePriceValue;
|
||||
auto const actual = rebuiltTx.getPurchasePrice();
|
||||
expectEqualField(expected, actual, "sfPurchasePrice");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = interestRateValue;
|
||||
auto const actual = rebuiltTx.getInterestRate();
|
||||
expectEqualField(expected, actual, "sfInterestRate");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = expirationValue;
|
||||
auto const actual = rebuiltTx.getExpiration();
|
||||
expectEqualField(expected, actual, "sfExpiration");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = maturityDateValue;
|
||||
auto const actual = rebuiltTx.getMaturityDate();
|
||||
expectEqualField(expected, actual, "sfMaturityDate");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gracePeriodValue;
|
||||
auto const actual = rebuiltTx.getGracePeriod();
|
||||
expectEqualField(expected, actual, "sfGracePeriod");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = dataValue;
|
||||
auto const actualOpt = rebuiltTx.getData();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfData should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfData");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsRepoCreateTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(RepoCreate{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsRepoCreateTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(RepoCreateBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(TransactionsRepoCreateTests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testRepoCreateNullopt"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 3;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific required field values
|
||||
auto const counterpartyValue = canonical_ACCOUNT();
|
||||
auto const collateralAmountValue = canonical_AMOUNT();
|
||||
auto const purchasePriceValue = canonical_AMOUNT();
|
||||
auto const interestRateValue = canonical_UINT32();
|
||||
auto const expirationValue = canonical_UINT32();
|
||||
auto const maturityDateValue = canonical_UINT32();
|
||||
auto const gracePeriodValue = canonical_UINT32();
|
||||
|
||||
RepoCreateBuilder builder{
|
||||
accountValue,
|
||||
counterpartyValue,
|
||||
collateralAmountValue,
|
||||
purchasePriceValue,
|
||||
interestRateValue,
|
||||
expirationValue,
|
||||
maturityDateValue,
|
||||
gracePeriodValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Do NOT set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(tx.hasData());
|
||||
EXPECT_FALSE(tx.getData().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Auto-generated unit tests for transaction RepoDefault
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/RepoDefault.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsRepoDefaultTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testRepoDefault"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const repoIDValue = canonical_UINT256();
|
||||
|
||||
RepoDefaultBuilder builder{
|
||||
accountValue,
|
||||
repoIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = repoIDValue;
|
||||
auto const actual = tx.getRepoID();
|
||||
expectEqualField(expected, actual, "sfRepoID");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsRepoDefaultTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testRepoDefaultFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const repoIDValue = canonical_UINT256();
|
||||
|
||||
// Build an initial transaction
|
||||
RepoDefaultBuilder initialBuilder{
|
||||
accountValue,
|
||||
repoIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
RepoDefaultBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = repoIDValue;
|
||||
auto const actual = rebuiltTx.getRepoID();
|
||||
expectEqualField(expected, actual, "sfRepoID");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsRepoDefaultTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(RepoDefault{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsRepoDefaultTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(RepoDefaultBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -212,6 +212,30 @@ parseCredential(
|
||||
return keylet::credential(*subject, *issuer, Slice(credType->data(), credType->size())).key;
|
||||
}
|
||||
|
||||
static std::expected<uint256, json::Value>
|
||||
parseRepo(
|
||||
json::Value const& params,
|
||||
json::StaticString const fieldName,
|
||||
[[maybe_unused]] unsigned const apiVersion)
|
||||
{
|
||||
if (!params.isObject())
|
||||
{
|
||||
return parseObjectID(params, fieldName);
|
||||
}
|
||||
|
||||
auto const seller =
|
||||
ledger_entry_helpers::requiredAccountID(params, jss::account, "malformedAddress");
|
||||
if (!seller)
|
||||
return std::unexpected(seller.error());
|
||||
|
||||
if (!params.isMember(jss::seq) || !params[jss::seq].isIntegral())
|
||||
{
|
||||
return ledger_entry_helpers::invalidFieldError("malformedRequest", jss::seq, "number");
|
||||
}
|
||||
|
||||
return keylet::repo(*seller, SeqProxy::rawSequence(params[jss::seq].asUInt())).key;
|
||||
}
|
||||
|
||||
static std::expected<uint256, json::Value>
|
||||
parseDelegate(
|
||||
json::Value const& params,
|
||||
|
||||
Reference in New Issue
Block a user