mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 02:25:52 +00:00
[WIP] Continue implementing LoanSet Transactor
- Needs tests - Started trying to add strong typing to the fields used as bips or 1/10 bips.
This commit is contained in:
@@ -349,7 +349,10 @@ loanbroker(uint256 const& vaultKey)
|
||||
}
|
||||
|
||||
Keylet
|
||||
loan(AccountID const& owner, uint256 loanBrokerID, std::uint32_t seq) noexcept;
|
||||
loan(
|
||||
AccountID const& borrower,
|
||||
uint256 loanBrokerID,
|
||||
std::uint32_t seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
loan(uint256 const& vaultKey)
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/partitioned_unordered_map.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -98,70 +100,73 @@ std::uint16_t constexpr maxTransferFee = 50000;
|
||||
*
|
||||
* Example: 50% is 0.50 * bipsPerUnity = 5,000 bps.
|
||||
*/
|
||||
std::uint32_t constexpr bipsPerUnity = 100 * 100;
|
||||
std::uint32_t constexpr tenthBipsPerUnity = bipsPerUnity * 10;
|
||||
Bips32 constexpr bipsPerUnity(100 * 100);
|
||||
TenthBips32 constexpr tenthBipsPerUnity(bipsPerUnity.value() * 10);
|
||||
|
||||
constexpr std::uint32_t
|
||||
constexpr Bips32
|
||||
percentageToBips(std::uint32_t percentage)
|
||||
{
|
||||
return percentage * bipsPerUnity / 100;
|
||||
return Bips32(percentage * bipsPerUnity.value() / 100);
|
||||
}
|
||||
constexpr std::uint32_t
|
||||
constexpr TenthBips32
|
||||
percentageToTenthBips(std::uint32_t percentage)
|
||||
{
|
||||
return percentage * tenthBipsPerUnity / 100;
|
||||
return TenthBips32(percentage * tenthBipsPerUnity.value() / 100);
|
||||
}
|
||||
template <typename T>
|
||||
template <typename T, class TBips>
|
||||
constexpr T
|
||||
bipsOfValue(T value, std::uint32_t bips)
|
||||
bipsOfValue(T value, Bips<TBips> bips)
|
||||
{
|
||||
return value * bips / bipsPerUnity;
|
||||
return value * bips.value() / bipsPerUnity.value();
|
||||
}
|
||||
template <typename T>
|
||||
template <typename T, class TBips>
|
||||
constexpr T
|
||||
tenthBipsOfValue(T value, std::uint32_t bips)
|
||||
tenthBipsOfValue(T value, TenthBips<TBips> bips)
|
||||
{
|
||||
return value * bips / tenthBipsPerUnity;
|
||||
return value * bips.value() / tenthBipsPerUnity.value();
|
||||
}
|
||||
|
||||
/** The maximum management fee rate allowed by a loan broker in 1/10 bips.
|
||||
|
||||
Valid values are between 0 and 10% inclusive.
|
||||
*/
|
||||
std::uint16_t constexpr maxManagementFeeRate = percentageToTenthBips(10);
|
||||
static_assert(maxManagementFeeRate == 10'000);
|
||||
TenthBips16 constexpr maxManagementFeeRate(
|
||||
unsafe_cast<std::uint16_t>(percentageToTenthBips(10).value()));
|
||||
static_assert(maxManagementFeeRate == TenthBips16(std::uint16_t(10'000u)));
|
||||
|
||||
/** The maximum coverage rate required of a loan broker in 1/10 bips.
|
||||
|
||||
Valid values are between 0 and 100% inclusive.
|
||||
*/
|
||||
std::uint32_t constexpr maxCoverRate = percentageToTenthBips(100);
|
||||
static_assert(maxCoverRate == 100'000);
|
||||
TenthBips32 constexpr maxCoverRate = percentageToTenthBips(100);
|
||||
static_assert(maxCoverRate == TenthBips32(100'000u));
|
||||
|
||||
/** The maximum overpayment fee on a loan in 1/10 bips.
|
||||
*
|
||||
Valid values are between 0 and 100% inclusive.
|
||||
*/
|
||||
std::uint32_t constexpr maxOverpaymentFee = percentageToTenthBips(100);
|
||||
TenthBips32 constexpr maxOverpaymentFee = percentageToTenthBips(100);
|
||||
|
||||
/** The maximum premium added to the interest rate for late payments on a loan
|
||||
* in 1/10 bips.
|
||||
*
|
||||
* Valid values are between 0 and 100% inclusive.
|
||||
*/
|
||||
std::uint32_t constexpr maxLateInterestRate = percentageToTenthBips(100);
|
||||
TenthBips32 constexpr maxLateInterestRate = percentageToTenthBips(100);
|
||||
|
||||
/** The maximum interest rate charged for repaying a loan early in 1/10 bips.
|
||||
/** The maximum close interest rate charged for repaying a loan early in 1/10
|
||||
* bips.
|
||||
*
|
||||
* Valid values are between 0 and 100% inclusive.
|
||||
*/
|
||||
std::uint32_t constexpr maxCloseInterestRate = percentageToTenthBips(100);
|
||||
TenthBips32 constexpr maxCloseInterestRate = percentageToTenthBips(100);
|
||||
|
||||
/** The maximum interest rate charged on loan overpayments in 1/10 bips.
|
||||
/** The maximum overpayment interest rate charged on loan overpayments in 1/10
|
||||
* bips.
|
||||
*
|
||||
* Valid values are between 0 and 100% inclusive.
|
||||
*/
|
||||
std::uint32_t constexpr maxOverpaymentInterestRate = percentageToTenthBips(100);
|
||||
TenthBips32 constexpr maxOverpaymentInterestRate = percentageToTenthBips(100);
|
||||
|
||||
/** The maximum length of a URI inside an NFT */
|
||||
std::size_t constexpr maxTokenURILength = 256;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
@@ -122,267 +123,287 @@ field_code(SerializedTypeID id, int index)
|
||||
return (safe_cast<int>(id) << 16) | index;
|
||||
}
|
||||
|
||||
// constexpr
|
||||
// constexpr
|
||||
inline int
|
||||
field_code(int id, int index)
|
||||
{
|
||||
return (id << 16) | index;
|
||||
}
|
||||
|
||||
/** Identifies fields.
|
||||
/** Identifies fields.
|
||||
|
||||
Fields are necessary to tag data in signed transactions so that
|
||||
the binary format of the transaction can be canonicalized. All
|
||||
SFields are created at compile time.
|
||||
Fields are necessary to tag data in signed transactions so that
|
||||
the binary format of the transaction can be canonicalized. All
|
||||
SFields are created at compile time.
|
||||
|
||||
Each SField, once constructed, lives until program termination, and there
|
||||
is only one instance per fieldType/fieldValue pair which serves the entire
|
||||
application.
|
||||
*/
|
||||
class SField
|
||||
{
|
||||
public:
|
||||
enum {
|
||||
sMD_Never = 0x00,
|
||||
sMD_ChangeOrig = 0x01, // original value when it changes
|
||||
sMD_ChangeNew = 0x02, // new value when it changes
|
||||
sMD_DeleteFinal = 0x04, // final value when it is deleted
|
||||
sMD_Create = 0x08, // value when it's created
|
||||
sMD_Always = 0x10, // value when node containing it is affected at all
|
||||
sMD_BaseTen =
|
||||
0x20, // value is treated as base 10, overriding default behavior
|
||||
sMD_PseudoAccount = 0x40, // if this field is set in an ACCOUNT_ROOT
|
||||
// _only_, then it is a pseudo-account
|
||||
sMD_Default =
|
||||
sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal | sMD_Create
|
||||
Each SField, once constructed, lives until program termination, and
|
||||
there is only one instance per fieldType/fieldValue pair which serves the
|
||||
entire application.
|
||||
*/
|
||||
class SField
|
||||
{
|
||||
public:
|
||||
enum {
|
||||
sMD_Never = 0x00,
|
||||
sMD_ChangeOrig = 0x01, // original value when it changes
|
||||
sMD_ChangeNew = 0x02, // new value when it changes
|
||||
sMD_DeleteFinal = 0x04, // final value when it is deleted
|
||||
sMD_Create = 0x08, // value when it's created
|
||||
sMD_Always =
|
||||
0x10, // value when node containing it is affected at all
|
||||
sMD_BaseTen = 0x20, // value is treated as base 10, overriding
|
||||
// default behavior
|
||||
sMD_PseudoAccount =
|
||||
0x40, // if this field is set in an ACCOUNT_ROOT
|
||||
// _only_, then it is a pseudo-account
|
||||
sMD_Default =
|
||||
sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal | sMD_Create
|
||||
};
|
||||
|
||||
enum class IsSigning : unsigned char { no, yes };
|
||||
static IsSigning const notSigning = IsSigning::no;
|
||||
|
||||
int const fieldCode; // (type<<16)|index
|
||||
SerializedTypeID const fieldType; // STI_*
|
||||
int const fieldValue; // Code number for protocol
|
||||
std::string const fieldName;
|
||||
int const fieldMeta;
|
||||
int const fieldNum;
|
||||
IsSigning const signingField;
|
||||
Json::StaticString const jsonName;
|
||||
|
||||
SField(SField const&) = delete;
|
||||
SField&
|
||||
operator=(SField const&) = delete;
|
||||
SField(SField&&) = delete;
|
||||
SField&
|
||||
operator=(SField&&) = delete;
|
||||
|
||||
public:
|
||||
struct private_access_tag_t; // public, but still an implementation
|
||||
// detail
|
||||
|
||||
// These constructors can only be called from SField.cpp
|
||||
SField(
|
||||
private_access_tag_t,
|
||||
SerializedTypeID tid,
|
||||
int fv,
|
||||
const char* fn,
|
||||
int meta = sMD_Default,
|
||||
IsSigning signing = IsSigning::yes);
|
||||
explicit SField(private_access_tag_t, int fc);
|
||||
|
||||
static const SField&
|
||||
getField(int fieldCode);
|
||||
static const SField&
|
||||
getField(std::string const& fieldName);
|
||||
static const SField&
|
||||
getField(int type, int value)
|
||||
{
|
||||
return getField(field_code(type, value));
|
||||
}
|
||||
|
||||
static const SField&
|
||||
getField(SerializedTypeID type, int value)
|
||||
{
|
||||
return getField(field_code(type, value));
|
||||
}
|
||||
|
||||
std::string const&
|
||||
getName() const
|
||||
{
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
bool
|
||||
hasName() const
|
||||
{
|
||||
return fieldCode > 0;
|
||||
}
|
||||
|
||||
Json::StaticString const&
|
||||
getJsonName() const
|
||||
{
|
||||
return jsonName;
|
||||
}
|
||||
|
||||
operator Json::StaticString const&() const
|
||||
{
|
||||
return jsonName;
|
||||
}
|
||||
|
||||
bool
|
||||
isInvalid() const
|
||||
{
|
||||
return fieldCode == -1;
|
||||
}
|
||||
|
||||
bool
|
||||
isUseful() const
|
||||
{
|
||||
return fieldCode > 0;
|
||||
}
|
||||
|
||||
bool
|
||||
isBinary() const
|
||||
{
|
||||
return fieldValue < 256;
|
||||
}
|
||||
|
||||
// A discardable field is one that cannot be serialized, and
|
||||
// should be discarded during serialization,like 'hash'.
|
||||
// You cannot serialize an object's hash inside that object,
|
||||
// but you can have it in the JSON representation.
|
||||
bool
|
||||
isDiscardable() const
|
||||
{
|
||||
return fieldValue > 256;
|
||||
}
|
||||
|
||||
int
|
||||
getCode() const
|
||||
{
|
||||
return fieldCode;
|
||||
}
|
||||
int
|
||||
getNum() const
|
||||
{
|
||||
return fieldNum;
|
||||
}
|
||||
static int
|
||||
getNumFields()
|
||||
{
|
||||
return num;
|
||||
}
|
||||
|
||||
bool
|
||||
shouldMeta(int c) const
|
||||
{
|
||||
return (fieldMeta & c) != 0;
|
||||
}
|
||||
|
||||
bool
|
||||
shouldInclude(bool withSigningField) const
|
||||
{
|
||||
return (fieldValue < 256) &&
|
||||
(withSigningField || (signingField == IsSigning::yes));
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(const SField& f) const
|
||||
{
|
||||
return fieldCode == f.fieldCode;
|
||||
}
|
||||
|
||||
bool
|
||||
operator!=(const SField& f) const
|
||||
{
|
||||
return fieldCode != f.fieldCode;
|
||||
}
|
||||
|
||||
static int
|
||||
compare(const SField& f1, const SField& f2);
|
||||
|
||||
static std::map<int, SField const*> const&
|
||||
getKnownCodeToField()
|
||||
{
|
||||
return knownCodeToField;
|
||||
}
|
||||
|
||||
private:
|
||||
static int num;
|
||||
static std::map<int, SField const*> knownCodeToField;
|
||||
static std::map<std::string, SField const*> knownNameToField;
|
||||
};
|
||||
|
||||
enum class IsSigning : unsigned char { no, yes };
|
||||
static IsSigning const notSigning = IsSigning::no;
|
||||
|
||||
int const fieldCode; // (type<<16)|index
|
||||
SerializedTypeID const fieldType; // STI_*
|
||||
int const fieldValue; // Code number for protocol
|
||||
std::string const fieldName;
|
||||
int const fieldMeta;
|
||||
int const fieldNum;
|
||||
IsSigning const signingField;
|
||||
Json::StaticString const jsonName;
|
||||
|
||||
SField(SField const&) = delete;
|
||||
SField&
|
||||
operator=(SField const&) = delete;
|
||||
SField(SField&&) = delete;
|
||||
SField&
|
||||
operator=(SField&&) = delete;
|
||||
|
||||
public:
|
||||
struct private_access_tag_t; // public, but still an implementation detail
|
||||
|
||||
// These constructors can only be called from SField.cpp
|
||||
SField(
|
||||
private_access_tag_t,
|
||||
SerializedTypeID tid,
|
||||
int fv,
|
||||
const char* fn,
|
||||
int meta = sMD_Default,
|
||||
IsSigning signing = IsSigning::yes);
|
||||
explicit SField(private_access_tag_t, int fc);
|
||||
|
||||
static const SField&
|
||||
getField(int fieldCode);
|
||||
static const SField&
|
||||
getField(std::string const& fieldName);
|
||||
static const SField&
|
||||
getField(int type, int value)
|
||||
/** A field with a type known at compile time. */
|
||||
template <class T>
|
||||
struct TypedField : SField
|
||||
{
|
||||
return getField(field_code(type, value));
|
||||
using type = T;
|
||||
|
||||
template <class... Args>
|
||||
explicit TypedField(private_access_tag_t pat, Args&&... args);
|
||||
};
|
||||
|
||||
/** Indicate std::optional field semantics. */
|
||||
template <class T>
|
||||
struct OptionaledField
|
||||
{
|
||||
TypedField<T> const* f;
|
||||
|
||||
explicit OptionaledField(TypedField<T> const& f_) : f(&f_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
inline OptionaledField<T>
|
||||
operator~(TypedField<T> const& f)
|
||||
{
|
||||
return OptionaledField<T>(f);
|
||||
}
|
||||
|
||||
static const SField&
|
||||
getField(SerializedTypeID type, int value)
|
||||
{
|
||||
return getField(field_code(type, value));
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::string const&
|
||||
getName() const
|
||||
{
|
||||
return fieldName;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
hasName() const
|
||||
{
|
||||
return fieldCode > 0;
|
||||
}
|
||||
using SF_UINT8 = TypedField<STInteger<std::uint8_t>>;
|
||||
using SF_UINT16 = TypedField<STInteger<std::uint16_t>>;
|
||||
using SF_UINT32 = TypedField<STInteger<std::uint32_t>>;
|
||||
using SF_UINT64 = TypedField<STInteger<std::uint64_t>>;
|
||||
using SF_UINT96 = TypedField<STBitString<96>>;
|
||||
using SF_UINT128 = TypedField<STBitString<128>>;
|
||||
using SF_UINT160 = TypedField<STBitString<160>>;
|
||||
using SF_UINT192 = TypedField<STBitString<192>>;
|
||||
using SF_UINT256 = TypedField<STBitString<256>>;
|
||||
using SF_UINT384 = TypedField<STBitString<384>>;
|
||||
using SF_UINT512 = TypedField<STBitString<512>>;
|
||||
|
||||
Json::StaticString const&
|
||||
getJsonName() const
|
||||
{
|
||||
return jsonName;
|
||||
}
|
||||
// These BIPS and TENTHBIPS values are serialized as the underlying type.
|
||||
// The tag is only applied when deserialized.
|
||||
//
|
||||
// Basis points (bips) values:
|
||||
using SF_BIPS16 = TypedField<STInteger<Bips16>>;
|
||||
using SF_BIPS32 = TypedField<STInteger<Bips32>>;
|
||||
// Tenth of a basis point values:
|
||||
using SF_TENTHBIPS16 = TypedField<STInteger<TenthBips16>>;
|
||||
using SF_TENTHBIPS32 = TypedField<STInteger<TenthBips32>>;
|
||||
|
||||
operator Json::StaticString const&() const
|
||||
{
|
||||
return jsonName;
|
||||
}
|
||||
using SF_ACCOUNT = TypedField<STAccount>;
|
||||
using SF_AMOUNT = TypedField<STAmount>;
|
||||
using SF_ISSUE = TypedField<STIssue>;
|
||||
using SF_CURRENCY = TypedField<STCurrency>;
|
||||
using SF_NUMBER = TypedField<STNumber>;
|
||||
using SF_VL = TypedField<STBlob>;
|
||||
using SF_VECTOR256 = TypedField<STVector256>;
|
||||
using SF_XCHAIN_BRIDGE = TypedField<STXChainBridge>;
|
||||
|
||||
bool
|
||||
isInvalid() const
|
||||
{
|
||||
return fieldCode == -1;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
isUseful() const
|
||||
{
|
||||
return fieldCode > 0;
|
||||
}
|
||||
|
||||
bool
|
||||
isBinary() const
|
||||
{
|
||||
return fieldValue < 256;
|
||||
}
|
||||
|
||||
// A discardable field is one that cannot be serialized, and
|
||||
// should be discarded during serialization,like 'hash'.
|
||||
// You cannot serialize an object's hash inside that object,
|
||||
// but you can have it in the JSON representation.
|
||||
bool
|
||||
isDiscardable() const
|
||||
{
|
||||
return fieldValue > 256;
|
||||
}
|
||||
|
||||
int
|
||||
getCode() const
|
||||
{
|
||||
return fieldCode;
|
||||
}
|
||||
int
|
||||
getNum() const
|
||||
{
|
||||
return fieldNum;
|
||||
}
|
||||
static int
|
||||
getNumFields()
|
||||
{
|
||||
return num;
|
||||
}
|
||||
|
||||
bool
|
||||
shouldMeta(int c) const
|
||||
{
|
||||
return (fieldMeta & c) != 0;
|
||||
}
|
||||
|
||||
bool
|
||||
shouldInclude(bool withSigningField) const
|
||||
{
|
||||
return (fieldValue < 256) &&
|
||||
(withSigningField || (signingField == IsSigning::yes));
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(const SField& f) const
|
||||
{
|
||||
return fieldCode == f.fieldCode;
|
||||
}
|
||||
|
||||
bool
|
||||
operator!=(const SField& f) const
|
||||
{
|
||||
return fieldCode != f.fieldCode;
|
||||
}
|
||||
|
||||
static int
|
||||
compare(const SField& f1, const SField& f2);
|
||||
|
||||
static std::map<int, SField const*> const&
|
||||
getKnownCodeToField()
|
||||
{
|
||||
return knownCodeToField;
|
||||
}
|
||||
|
||||
private:
|
||||
static int num;
|
||||
static std::map<int, SField const*> knownCodeToField;
|
||||
static std::map<std::string, SField const*> knownNameToField;
|
||||
};
|
||||
|
||||
/** A field with a type known at compile time. */
|
||||
template <class T>
|
||||
struct TypedField : SField
|
||||
{
|
||||
using type = T;
|
||||
|
||||
template <class... Args>
|
||||
explicit TypedField(private_access_tag_t pat, Args&&... args);
|
||||
};
|
||||
|
||||
/** Indicate std::optional field semantics. */
|
||||
template <class T>
|
||||
struct OptionaledField
|
||||
{
|
||||
TypedField<T> const* f;
|
||||
|
||||
explicit OptionaledField(TypedField<T> const& f_) : f(&f_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
inline OptionaledField<T>
|
||||
operator~(TypedField<T> const& f)
|
||||
{
|
||||
return OptionaledField<T>(f);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using SF_UINT8 = TypedField<STInteger<std::uint8_t>>;
|
||||
using SF_UINT16 = TypedField<STInteger<std::uint16_t>>;
|
||||
using SF_UINT32 = TypedField<STInteger<std::uint32_t>>;
|
||||
using SF_UINT64 = TypedField<STInteger<std::uint64_t>>;
|
||||
using SF_UINT96 = TypedField<STBitString<96>>;
|
||||
using SF_UINT128 = TypedField<STBitString<128>>;
|
||||
using SF_UINT160 = TypedField<STBitString<160>>;
|
||||
using SF_UINT192 = TypedField<STBitString<192>>;
|
||||
using SF_UINT256 = TypedField<STBitString<256>>;
|
||||
using SF_UINT384 = TypedField<STBitString<384>>;
|
||||
using SF_UINT512 = TypedField<STBitString<512>>;
|
||||
|
||||
using SF_ACCOUNT = TypedField<STAccount>;
|
||||
using SF_AMOUNT = TypedField<STAmount>;
|
||||
using SF_ISSUE = TypedField<STIssue>;
|
||||
using SF_CURRENCY = TypedField<STCurrency>;
|
||||
using SF_NUMBER = TypedField<STNumber>;
|
||||
using SF_VL = TypedField<STBlob>;
|
||||
using SF_VECTOR256 = TypedField<STVector256>;
|
||||
using SF_XCHAIN_BRIDGE = TypedField<STXChainBridge>;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Use macros for most SField construction to enforce naming conventions.
|
||||
// Use macros for most SField construction to enforce naming conventions.
|
||||
#pragma push_macro("UNTYPED_SFIELD")
|
||||
#undef UNTYPED_SFIELD
|
||||
#pragma push_macro("TYPED_SFIELD")
|
||||
#undef TYPED_SFIELD
|
||||
#pragma push_macro("INTERPRETED_SFIELD")
|
||||
#undef INTERPRETED_SFIELD
|
||||
|
||||
#define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \
|
||||
extern SField const sfName;
|
||||
extern SField const sfName;
|
||||
#define TYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \
|
||||
extern SF_##stiSuffix const sfName;
|
||||
extern SF_##stiSuffix const sfName;
|
||||
#define INTERPRETED_SFIELD( \
|
||||
sfName, stiSuffix, fieldValue, stiInterpreted, ...) \
|
||||
extern SF_##stiInterpreted const sfName;
|
||||
|
||||
extern SField const sfInvalid;
|
||||
extern SField const sfGeneric;
|
||||
extern SField const sfInvalid;
|
||||
extern SField const sfGeneric;
|
||||
|
||||
#include <xrpl/protocol/detail/sfields.macro>
|
||||
|
||||
#undef INTERPRETED_SFIELD
|
||||
#pragma pop_macro("INTERPRETED_SFIELD")
|
||||
#undef TYPED_SFIELD
|
||||
#pragma pop_macro("TYPED_SFIELD")
|
||||
#undef UNTYPED_SFIELD
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#define RIPPLE_PROTOCOL_STINTEGER_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/STBase.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -81,6 +82,13 @@ using STUInt16 = STInteger<std::uint16_t>;
|
||||
using STUInt32 = STInteger<std::uint32_t>;
|
||||
using STUInt64 = STInteger<std::uint64_t>;
|
||||
|
||||
// Basis points (bips) values:
|
||||
using SFBips16 = STInteger<Bips16>;
|
||||
using SFBips32 = STInteger<Bips32>;
|
||||
// Tenth of a basis point values:
|
||||
using SFTenthBips16 = STInteger<TenthBips16>;
|
||||
using SFTenthBips32 = STInteger<TenthBips32>;
|
||||
|
||||
template <typename Integer>
|
||||
inline STInteger<Integer>::STInteger(Integer v) : value_(v)
|
||||
{
|
||||
@@ -122,7 +130,7 @@ template <typename Integer>
|
||||
inline bool
|
||||
STInteger<Integer>::isDefault() const
|
||||
{
|
||||
return value_ == 0;
|
||||
return value_ == Integer{};
|
||||
}
|
||||
|
||||
template <typename Integer>
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/FeeUnits.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/SOTemplate.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -34,6 +33,7 @@
|
||||
#include <xrpl/protocol/STIssue.h>
|
||||
#include <xrpl/protocol/STPathSet.h>
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
#include <xrpl/protocol/detail/STVar.h>
|
||||
|
||||
#include <boost/iterator/transform_iterator.hpp>
|
||||
@@ -518,7 +518,8 @@ protected:
|
||||
// Constraint += and -= ValueProxy operators
|
||||
// to value types that support arithmetic operations
|
||||
template <typename U>
|
||||
concept IsArithmetic = std::is_arithmetic_v<U> || std::is_same_v<U, STAmount>;
|
||||
concept IsArithmetic = std::is_arithmetic_v<U> || std::is_same_v<U, STAmount> ||
|
||||
std::is_same_v<U, STNumber> || std::is_same_v<U, Number>;
|
||||
|
||||
template <class T>
|
||||
class STObject::ValueProxy : public Proxy<T>
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/FeeUnits.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef BASICS_FEES_H_INCLUDED
|
||||
#define BASICS_FEES_H_INCLUDED
|
||||
#ifndef PROTOCOL_UNITS_H_INCLUDED
|
||||
#define PROTOCOL_UNITS_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
@@ -38,23 +38,23 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace feeunit {
|
||||
namespace unit {
|
||||
|
||||
/** "drops" are the smallest divisible amount of XRP. This is what most
|
||||
of the code uses. */
|
||||
struct dropTag;
|
||||
/** "fee units" calculations are a not-really-unitless value that is used
|
||||
to express the cost of a given transaction vs. a reference transaction.
|
||||
They are primarily used by the Transactor classes. */
|
||||
struct feeunitTag;
|
||||
/** "fee levels" are used by the transaction queue to compare the relative
|
||||
cost of transactions that require different levels of effort to process.
|
||||
See also: src/ripple/app/misc/FeeEscalation.md#fee-level */
|
||||
struct feelevelTag;
|
||||
/** unitless values are plain scalars wrapped in a TaggedFee. They are
|
||||
/** unitless values are plain scalars wrapped in a ValueUnit. They are
|
||||
used for calculations in this header. */
|
||||
struct unitlessTag;
|
||||
|
||||
/** Units to represent basis points (bips) and 1/10 basis points */
|
||||
class BipsTag;
|
||||
class TenthBipsTag;
|
||||
|
||||
template <class T>
|
||||
using enable_if_unit_t = typename std::enable_if_t<
|
||||
std::is_class_v<T> && std::is_object_v<typename T::unit_type> &&
|
||||
@@ -62,32 +62,33 @@ using enable_if_unit_t = typename std::enable_if_t<
|
||||
|
||||
/** `is_usable_unit_v` is checked to ensure that only values with
|
||||
known valid type tags can be used (sometimes transparently) in
|
||||
non-fee contexts. At the time of implementation, this includes
|
||||
non-unit contexts. At the time of implementation, this includes
|
||||
all known tags, but more may be added in the future, and they
|
||||
should not be added automatically unless determined to be
|
||||
appropriate.
|
||||
*/
|
||||
template <class T, class = enable_if_unit_t<T>>
|
||||
constexpr bool is_usable_unit_v =
|
||||
std::is_same_v<typename T::unit_type, feeunitTag> ||
|
||||
std::is_same_v<typename T::unit_type, feelevelTag> ||
|
||||
std::is_same_v<typename T::unit_type, unitlessTag> ||
|
||||
std::is_same_v<typename T::unit_type, dropTag>;
|
||||
std::is_same_v<typename T::unit_type, dropTag> ||
|
||||
std::is_same_v<typename T::unit_type, BipsTag> ||
|
||||
std::is_same_v<typename T::unit_type, TenthBipsTag>;
|
||||
|
||||
template <class UnitTag, class T>
|
||||
class TaggedFee : private boost::totally_ordered<TaggedFee<UnitTag, T>>,
|
||||
private boost::additive<TaggedFee<UnitTag, T>>,
|
||||
private boost::equality_comparable<TaggedFee<UnitTag, T>, T>,
|
||||
private boost::dividable<TaggedFee<UnitTag, T>, T>,
|
||||
private boost::modable<TaggedFee<UnitTag, T>, T>,
|
||||
private boost::unit_steppable<TaggedFee<UnitTag, T>>
|
||||
class ValueUnit : private boost::totally_ordered<ValueUnit<UnitTag, T>>,
|
||||
private boost::additive<ValueUnit<UnitTag, T>>,
|
||||
private boost::equality_comparable<ValueUnit<UnitTag, T>, T>,
|
||||
private boost::dividable<ValueUnit<UnitTag, T>, T>,
|
||||
private boost::modable<ValueUnit<UnitTag, T>, T>,
|
||||
private boost::unit_steppable<ValueUnit<UnitTag, T>>
|
||||
{
|
||||
public:
|
||||
using unit_type = UnitTag;
|
||||
using value_type = T;
|
||||
|
||||
private:
|
||||
value_type fee_;
|
||||
value_type value_;
|
||||
|
||||
protected:
|
||||
template <class Other>
|
||||
@@ -95,44 +96,44 @@ protected:
|
||||
std::is_arithmetic_v<Other> && std::is_arithmetic_v<value_type> &&
|
||||
std::is_convertible_v<Other, value_type>;
|
||||
|
||||
template <class OtherFee, class = enable_if_unit_t<OtherFee>>
|
||||
static constexpr bool is_compatiblefee_v =
|
||||
is_compatible_v<typename OtherFee::value_type> &&
|
||||
std::is_same_v<UnitTag, typename OtherFee::unit_type>;
|
||||
template <class OtherValue, class = enable_if_unit_t<OtherValue>>
|
||||
static constexpr bool is_compatiblevalue_v =
|
||||
is_compatible_v<typename OtherValue::value_type> &&
|
||||
std::is_same_v<UnitTag, typename OtherValue::unit_type>;
|
||||
|
||||
template <class Other>
|
||||
using enable_if_compatible_t =
|
||||
typename std::enable_if_t<is_compatible_v<Other>>;
|
||||
|
||||
template <class OtherFee>
|
||||
using enable_if_compatiblefee_t =
|
||||
typename std::enable_if_t<is_compatiblefee_v<OtherFee>>;
|
||||
template <class OtherValue>
|
||||
using enable_if_compatiblevalue_t =
|
||||
typename std::enable_if_t<is_compatiblevalue_v<OtherValue>>;
|
||||
|
||||
public:
|
||||
TaggedFee() = default;
|
||||
constexpr TaggedFee(TaggedFee const& other) = default;
|
||||
constexpr TaggedFee&
|
||||
operator=(TaggedFee const& other) = default;
|
||||
ValueUnit() = default;
|
||||
constexpr ValueUnit(ValueUnit const& other) = default;
|
||||
constexpr ValueUnit&
|
||||
operator=(ValueUnit const& other) = default;
|
||||
|
||||
constexpr explicit TaggedFee(beast::Zero) : fee_(0)
|
||||
constexpr explicit ValueUnit(beast::Zero) : value_(0)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr TaggedFee&
|
||||
constexpr ValueUnit&
|
||||
operator=(beast::Zero)
|
||||
{
|
||||
fee_ = 0;
|
||||
value_ = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr explicit TaggedFee(value_type fee) : fee_(fee)
|
||||
constexpr explicit ValueUnit(value_type value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
TaggedFee&
|
||||
operator=(value_type fee)
|
||||
constexpr ValueUnit&
|
||||
operator=(value_type value)
|
||||
{
|
||||
fee_ = fee;
|
||||
value_ = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -144,153 +145,180 @@ public:
|
||||
class = std::enable_if_t<
|
||||
is_compatible_v<Other> &&
|
||||
is_safetocasttovalue_v<value_type, Other>>>
|
||||
constexpr TaggedFee(TaggedFee<unit_type, Other> const& fee)
|
||||
: TaggedFee(safe_cast<value_type>(fee.fee()))
|
||||
constexpr ValueUnit(ValueUnit<unit_type, Other> const& value)
|
||||
: ValueUnit(safe_cast<value_type>(value.value()))
|
||||
{
|
||||
}
|
||||
|
||||
constexpr TaggedFee
|
||||
constexpr ValueUnit
|
||||
operator+(value_type const& rhs) const
|
||||
{
|
||||
return ValueUnit{value_ + rhs};
|
||||
}
|
||||
|
||||
friend constexpr ValueUnit
|
||||
operator+(value_type lhs, ValueUnit const& rhs)
|
||||
{
|
||||
// addition is commutative
|
||||
return rhs + lhs;
|
||||
}
|
||||
|
||||
constexpr ValueUnit
|
||||
operator-(value_type const& rhs) const
|
||||
{
|
||||
return ValueUnit{value_ - rhs};
|
||||
}
|
||||
|
||||
friend constexpr ValueUnit
|
||||
operator-(value_type lhs, ValueUnit const& rhs)
|
||||
{
|
||||
// subtraction is NOT commutative, but (lhs + (-rhs)) is addition, which
|
||||
// is
|
||||
return -rhs + lhs;
|
||||
}
|
||||
|
||||
constexpr ValueUnit
|
||||
operator*(value_type const& rhs) const
|
||||
{
|
||||
return TaggedFee{fee_ * rhs};
|
||||
return ValueUnit{value_ * rhs};
|
||||
}
|
||||
|
||||
friend constexpr TaggedFee
|
||||
operator*(value_type lhs, TaggedFee const& rhs)
|
||||
friend constexpr ValueUnit
|
||||
operator*(value_type lhs, ValueUnit const& rhs)
|
||||
{
|
||||
// multiplication is commutative
|
||||
return rhs * lhs;
|
||||
}
|
||||
|
||||
constexpr value_type
|
||||
operator/(TaggedFee const& rhs) const
|
||||
operator/(ValueUnit const& rhs) const
|
||||
{
|
||||
return fee_ / rhs.fee_;
|
||||
return value_ / rhs.value_;
|
||||
}
|
||||
|
||||
TaggedFee&
|
||||
operator+=(TaggedFee const& other)
|
||||
ValueUnit&
|
||||
operator+=(ValueUnit const& other)
|
||||
{
|
||||
fee_ += other.fee();
|
||||
value_ += other.value();
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaggedFee&
|
||||
operator-=(TaggedFee const& other)
|
||||
ValueUnit&
|
||||
operator-=(ValueUnit const& other)
|
||||
{
|
||||
fee_ -= other.fee();
|
||||
value_ -= other.value();
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaggedFee&
|
||||
ValueUnit&
|
||||
operator++()
|
||||
{
|
||||
++fee_;
|
||||
++value_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaggedFee&
|
||||
ValueUnit&
|
||||
operator--()
|
||||
{
|
||||
--fee_;
|
||||
--value_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaggedFee&
|
||||
ValueUnit&
|
||||
operator*=(value_type const& rhs)
|
||||
{
|
||||
fee_ *= rhs;
|
||||
value_ *= rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaggedFee&
|
||||
ValueUnit&
|
||||
operator/=(value_type const& rhs)
|
||||
{
|
||||
fee_ /= rhs;
|
||||
value_ /= rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class transparent = value_type>
|
||||
std::enable_if_t<std::is_integral_v<transparent>, TaggedFee&>
|
||||
std::enable_if_t<std::is_integral_v<transparent>, ValueUnit&>
|
||||
operator%=(value_type const& rhs)
|
||||
{
|
||||
fee_ %= rhs;
|
||||
value_ %= rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaggedFee
|
||||
ValueUnit
|
||||
operator-() const
|
||||
{
|
||||
static_assert(
|
||||
std::is_signed_v<T>, "- operator illegal on unsigned fee types");
|
||||
return TaggedFee{-fee_};
|
||||
std::is_signed_v<T>, "- operator illegal on unsigned value types");
|
||||
return ValueUnit{-value_};
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(TaggedFee const& other) const
|
||||
constexpr bool
|
||||
operator==(ValueUnit const& other) const
|
||||
{
|
||||
return fee_ == other.fee_;
|
||||
return value_ == other.value_;
|
||||
}
|
||||
|
||||
template <class Other, class = enable_if_compatible_t<Other>>
|
||||
bool
|
||||
operator==(TaggedFee<unit_type, Other> const& other) const
|
||||
constexpr bool
|
||||
operator==(ValueUnit<unit_type, Other> const& other) const
|
||||
{
|
||||
return fee_ == other.fee();
|
||||
return value_ == other.value();
|
||||
}
|
||||
|
||||
bool
|
||||
constexpr bool
|
||||
operator==(value_type other) const
|
||||
{
|
||||
return fee_ == other;
|
||||
return value_ == other;
|
||||
}
|
||||
|
||||
template <class Other, class = enable_if_compatible_t<Other>>
|
||||
bool
|
||||
operator!=(TaggedFee<unit_type, Other> const& other) const
|
||||
constexpr bool
|
||||
operator!=(ValueUnit<unit_type, Other> const& other) const
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
bool
|
||||
operator<(TaggedFee const& other) const
|
||||
constexpr bool
|
||||
operator<(ValueUnit const& other) const
|
||||
{
|
||||
return fee_ < other.fee_;
|
||||
return value_ < other.value_;
|
||||
}
|
||||
|
||||
/** Returns true if the amount is not zero */
|
||||
explicit constexpr
|
||||
operator bool() const noexcept
|
||||
{
|
||||
return fee_ != 0;
|
||||
return value_ != 0;
|
||||
}
|
||||
|
||||
/** Return the sign of the amount */
|
||||
constexpr int
|
||||
signum() const noexcept
|
||||
{
|
||||
return (fee_ < 0) ? -1 : (fee_ ? 1 : 0);
|
||||
return (value_ < 0) ? -1 : (value_ ? 1 : 0);
|
||||
}
|
||||
|
||||
/** Returns the number of drops */
|
||||
constexpr value_type
|
||||
fee() const
|
||||
{
|
||||
return fee_;
|
||||
return value_;
|
||||
}
|
||||
|
||||
template <class Other>
|
||||
constexpr double
|
||||
decimalFromReference(TaggedFee<unit_type, Other> reference) const
|
||||
decimalFromReference(ValueUnit<unit_type, Other> reference) const
|
||||
{
|
||||
return static_cast<double>(fee_) / reference.fee();
|
||||
return static_cast<double>(value_) / reference.value();
|
||||
}
|
||||
|
||||
// `is_usable_unit_v` is checked to ensure that only values with
|
||||
// known valid type tags can be converted to JSON. At the time
|
||||
// of implementation, that includes all known tags, but more may
|
||||
// be added in the future.
|
||||
std::enable_if_t<is_usable_unit_v<TaggedFee>, Json::Value>
|
||||
std::enable_if_t<is_usable_unit_v<ValueUnit>, Json::Value>
|
||||
jsonClipped() const
|
||||
{
|
||||
if constexpr (std::is_integral_v<value_type>)
|
||||
@@ -303,15 +331,15 @@ public:
|
||||
constexpr auto min = std::numeric_limits<jsontype>::min();
|
||||
constexpr auto max = std::numeric_limits<jsontype>::max();
|
||||
|
||||
if (fee_ < min)
|
||||
if (value_ < min)
|
||||
return min;
|
||||
if (fee_ > max)
|
||||
if (value_ > max)
|
||||
return max;
|
||||
return static_cast<jsontype>(fee_);
|
||||
return static_cast<jsontype>(value_);
|
||||
}
|
||||
else
|
||||
{
|
||||
return fee_;
|
||||
return value_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,30 +350,30 @@ public:
|
||||
constexpr value_type
|
||||
value() const
|
||||
{
|
||||
return fee_;
|
||||
return value_;
|
||||
}
|
||||
|
||||
friend std::istream&
|
||||
operator>>(std::istream& s, TaggedFee& val)
|
||||
operator>>(std::istream& s, ValueUnit& val)
|
||||
{
|
||||
s >> val.fee_;
|
||||
s >> val.value_;
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
// Output Fees as just their numeric value.
|
||||
// Output Values as just their numeric value.
|
||||
template <class Char, class Traits, class UnitTag, class T>
|
||||
std::basic_ostream<Char, Traits>&
|
||||
operator<<(std::basic_ostream<Char, Traits>& os, const TaggedFee<UnitTag, T>& q)
|
||||
operator<<(std::basic_ostream<Char, Traits>& os, const ValueUnit<UnitTag, T>& q)
|
||||
{
|
||||
return os << q.value();
|
||||
}
|
||||
|
||||
template <class UnitTag, class T>
|
||||
std::string
|
||||
to_string(TaggedFee<UnitTag, T> const& amount)
|
||||
to_string(ValueUnit<UnitTag, T> const& amount)
|
||||
{
|
||||
return std::to_string(amount.fee());
|
||||
return std::to_string(amount.value());
|
||||
}
|
||||
|
||||
template <class Source, class = enable_if_unit_t<Source>>
|
||||
@@ -408,10 +436,10 @@ using enable_muldiv_commute_t =
|
||||
typename std::enable_if_t<can_muldiv_commute_v<Source1, Source2, Dest>>;
|
||||
|
||||
template <class T>
|
||||
TaggedFee<unitlessTag, T>
|
||||
ValueUnit<unitlessTag, T>
|
||||
scalar(T value)
|
||||
{
|
||||
return TaggedFee<unitlessTag, T>{value};
|
||||
return ValueUnit<unitlessTag, T>{value};
|
||||
}
|
||||
|
||||
template <
|
||||
@@ -422,18 +450,17 @@ template <
|
||||
std::optional<Dest>
|
||||
mulDivU(Source1 value, Dest mul, Source2 div)
|
||||
{
|
||||
// Fees can never be negative in any context.
|
||||
// values can never be negative in any context.
|
||||
if (value.value() < 0 || mul.value() < 0 || div.value() < 0)
|
||||
{
|
||||
// split the asserts so if one hits, the user can tell which
|
||||
// without a debugger.
|
||||
XRPL_ASSERT(
|
||||
value.value() >= 0,
|
||||
"ripple::feeunit::mulDivU : minimum value input");
|
||||
value.value() >= 0, "ripple::unit::mulDivU : minimum value input");
|
||||
XRPL_ASSERT(
|
||||
mul.value() >= 0, "ripple::feeunit::mulDivU : minimum mul input");
|
||||
mul.value() >= 0, "ripple::unit::mulDivU : minimum mul input");
|
||||
XRPL_ASSERT(
|
||||
div.value() >= 0, "ripple::feeunit::mulDivU : minimum div input");
|
||||
div.value() >= 0, "ripple::unit::mulDivU : minimum div input");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -466,46 +493,57 @@ mulDivU(Source1 value, Dest mul, Source2 div)
|
||||
return Dest{static_cast<desttype>(quotient)};
|
||||
}
|
||||
|
||||
} // namespace feeunit
|
||||
} // namespace unit
|
||||
|
||||
// Fee Levels
|
||||
template <class T>
|
||||
using FeeLevel = feeunit::TaggedFee<feeunit::feelevelTag, T>;
|
||||
using FeeLevel = unit::ValueUnit<unit::feelevelTag, T>;
|
||||
using FeeLevel64 = FeeLevel<std::uint64_t>;
|
||||
using FeeLevelDouble = FeeLevel<double>;
|
||||
|
||||
// Basis points (Bips)
|
||||
template <class T>
|
||||
using Bips = unit::ValueUnit<unit::BipsTag, T>;
|
||||
using Bips16 = Bips<std::uint16_t>;
|
||||
using Bips32 = Bips<std::uint32_t>;
|
||||
template <class T>
|
||||
using TenthBips = unit::ValueUnit<unit::TenthBipsTag, T>;
|
||||
using TenthBips16 = TenthBips<std::uint16_t>;
|
||||
using TenthBips32 = TenthBips<std::uint32_t>;
|
||||
|
||||
template <
|
||||
class Source1,
|
||||
class Source2,
|
||||
class Dest,
|
||||
class = feeunit::enable_muldiv_t<Source1, Source2, Dest>>
|
||||
class = unit::enable_muldiv_t<Source1, Source2, Dest>>
|
||||
std::optional<Dest>
|
||||
mulDiv(Source1 value, Dest mul, Source2 div)
|
||||
{
|
||||
return feeunit::mulDivU(value, mul, div);
|
||||
return unit::mulDivU(value, mul, div);
|
||||
}
|
||||
|
||||
template <
|
||||
class Source1,
|
||||
class Source2,
|
||||
class Dest,
|
||||
class = feeunit::enable_muldiv_commute_t<Source1, Source2, Dest>>
|
||||
class = unit::enable_muldiv_commute_t<Source1, Source2, Dest>>
|
||||
std::optional<Dest>
|
||||
mulDiv(Dest value, Source1 mul, Source2 div)
|
||||
{
|
||||
// Multiplication is commutative
|
||||
return feeunit::mulDivU(mul, value, div);
|
||||
return unit::mulDivU(mul, value, div);
|
||||
}
|
||||
|
||||
template <class Dest, class = feeunit::enable_muldiv_dest_t<Dest>>
|
||||
template <class Dest, class = unit::enable_muldiv_dest_t<Dest>>
|
||||
std::optional<Dest>
|
||||
mulDiv(std::uint64_t value, Dest mul, std::uint64_t div)
|
||||
{
|
||||
// Give the scalars a non-tag so the
|
||||
// unit-handling version gets called.
|
||||
return feeunit::mulDivU(feeunit::scalar(value), mul, feeunit::scalar(div));
|
||||
return unit::mulDivU(unit::scalar(value), mul, unit::scalar(div));
|
||||
}
|
||||
|
||||
template <class Dest, class = feeunit::enable_muldiv_dest_t<Dest>>
|
||||
template <class Dest, class = unit::enable_muldiv_dest_t<Dest>>
|
||||
std::optional<Dest>
|
||||
mulDiv(Dest value, std::uint64_t mul, std::uint64_t div)
|
||||
{
|
||||
@@ -516,13 +554,13 @@ mulDiv(Dest value, std::uint64_t mul, std::uint64_t div)
|
||||
template <
|
||||
class Source1,
|
||||
class Source2,
|
||||
class = feeunit::enable_muldiv_sources_t<Source1, Source2>>
|
||||
class = unit::enable_muldiv_sources_t<Source1, Source2>>
|
||||
std::optional<std::uint64_t>
|
||||
mulDiv(Source1 value, std::uint64_t mul, Source2 div)
|
||||
{
|
||||
// Give the scalars a dimensionless unit so the
|
||||
// unit-handling version gets called.
|
||||
auto unitresult = feeunit::mulDivU(value, feeunit::scalar(mul), div);
|
||||
auto unitresult = unit::mulDivU(value, unit::scalar(mul), div);
|
||||
|
||||
if (!unitresult)
|
||||
return std::nullopt;
|
||||
@@ -533,7 +571,7 @@ mulDiv(Source1 value, std::uint64_t mul, Source2 div)
|
||||
template <
|
||||
class Source1,
|
||||
class Source2,
|
||||
class = feeunit::enable_muldiv_sources_t<Source1, Source2>>
|
||||
class = unit::enable_muldiv_sources_t<Source1, Source2>>
|
||||
std::optional<std::uint64_t>
|
||||
mulDiv(std::uint64_t value, Source1 mul, Source2 div)
|
||||
{
|
||||
@@ -567,4 +605,4 @@ unsafe_cast(Src s) noexcept
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif // BASICS_FEES_H_INCLUDED
|
||||
#endif // PROTOCOL_UNITS_H_INCLUDED
|
||||
@@ -24,7 +24,7 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/FeeUnits.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
#include <boost/operators.hpp>
|
||||
@@ -42,7 +42,7 @@ class XRPAmount : private boost::totally_ordered<XRPAmount>,
|
||||
private boost::additive<XRPAmount, std::int64_t>
|
||||
{
|
||||
public:
|
||||
using unit_type = feeunit::dropTag;
|
||||
using unit_type = unit::dropTag;
|
||||
using value_type = std::int64_t;
|
||||
|
||||
private:
|
||||
|
||||
@@ -61,7 +61,7 @@ TYPED_SFIELD(sfHookEmitCount, UINT16, 18)
|
||||
TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19)
|
||||
TYPED_SFIELD(sfHookApiVersion, UINT16, 20)
|
||||
TYPED_SFIELD(sfLedgerFixType, UINT16, 21)
|
||||
TYPED_SFIELD(sfManagementFeeRate, UINT16, 22)
|
||||
INTERPRETED_SFIELD(sfManagementFeeRate, UINT16, 22, TENTHBIPS16)
|
||||
|
||||
// 32-bit integers (common)
|
||||
TYPED_SFIELD(sfNetworkID, UINT32, 1)
|
||||
@@ -123,12 +123,12 @@ TYPED_SFIELD(sfPreviousPaymentDate, UINT32, 55)
|
||||
TYPED_SFIELD(sfNextPaymentDueDate, UINT32, 56)
|
||||
TYPED_SFIELD(sfPaymentRemaining, UINT32, 57)
|
||||
TYPED_SFIELD(sfPaymentTotal, UINT32, 58)
|
||||
TYPED_SFIELD(sfCoverRateMinimum, UINT32, 59)
|
||||
TYPED_SFIELD(sfCoverRateLiquidation, UINT32, 60)
|
||||
TYPED_SFIELD(sfInterestRate, UINT32, 61)
|
||||
TYPED_SFIELD(sfLateInterestRate, UINT32, 62)
|
||||
TYPED_SFIELD(sfCloseInterestRate, UINT32, 63)
|
||||
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 64)
|
||||
INTERPRETED_SFIELD(sfCoverRateMinimum, UINT32, 59, TENTHBIPS32)
|
||||
INTERPRETED_SFIELD(sfCoverRateLiquidation, UINT32, 60, TENTHBIPS32)
|
||||
INTERPRETED_SFIELD(sfInterestRate, UINT32, 61, TENTHBIPS32)
|
||||
INTERPRETED_SFIELD(sfLateInterestRate, UINT32, 62, TENTHBIPS32)
|
||||
INTERPRETED_SFIELD(sfCloseInterestRate, UINT32, 63, TENTHBIPS32)
|
||||
INTERPRETED_SFIELD(sfOverpaymentInterestRate, UINT32, 64, TENTHBIPS32)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
|
||||
@@ -739,9 +739,11 @@ TRANSACTION(ttLOAN_SET, 78, LoanSet, noPriv, ({
|
||||
{sfLoanServiceFee, soeOPTIONAL},
|
||||
{sfLatePaymentFee, soeOPTIONAL},
|
||||
{sfClosePaymentFee, soeOPTIONAL},
|
||||
{sfOverpaymentFee, soeOPTIONAL},
|
||||
{sfInterestRate, soeOPTIONAL},
|
||||
{sfLateInterestRate, soeOPTIONAL},
|
||||
{sfCloseInterestRate, soeOPTIONAL},
|
||||
{sfOverpaymentInterestRate, soeOPTIONAL},
|
||||
{sfPrincipalRequested, soeREQUIRED},
|
||||
{sfStartDate, soeREQUIRED},
|
||||
{sfPaymentTotal, soeOPTIONAL},
|
||||
|
||||
@@ -559,9 +559,12 @@ loanbroker(AccountID const& owner, std::uint32_t seq) noexcept
|
||||
}
|
||||
|
||||
Keylet
|
||||
loan(AccountID const& owner, uint256 loanBrokerID, std::uint32_t seq) noexcept
|
||||
loan(
|
||||
AccountID const& borrower,
|
||||
uint256 loanBrokerID,
|
||||
std::uint32_t seq) noexcept
|
||||
{
|
||||
return loan(indexHash(LedgerNameSpace::LOAN, loanBrokerID, seq));
|
||||
return loan(indexHash(LedgerNameSpace::LOAN, borrower, loanBrokerID, seq));
|
||||
}
|
||||
|
||||
Keylet
|
||||
|
||||
@@ -54,6 +54,8 @@ TypedField<T>::TypedField(private_access_tag_t pat, Args&&... args)
|
||||
#undef UNTYPED_SFIELD
|
||||
#pragma push_macro("TYPED_SFIELD")
|
||||
#undef TYPED_SFIELD
|
||||
#pragma push_macro("INTERPRETED_SFIELD")
|
||||
#undef INTERPRETED_SFIELD
|
||||
|
||||
#define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \
|
||||
SField const sfName( \
|
||||
@@ -69,6 +71,13 @@ TypedField<T>::TypedField(private_access_tag_t pat, Args&&... args)
|
||||
fieldValue, \
|
||||
std::string_view(#sfName).substr(2).data(), \
|
||||
##__VA_ARGS__);
|
||||
#define INTERPRETED_SFIELD(sfName, stiSuffix, fieldValue, stiInterpreted, ...) \
|
||||
SF_##stiInterpreted const sfName( \
|
||||
access, \
|
||||
STI_##stiSuffix, \
|
||||
fieldValue, \
|
||||
std::string_view(#sfName).substr(2).data(), \
|
||||
##__VA_ARGS__);
|
||||
|
||||
// SFields which, for historical reasons, do not follow naming conventions.
|
||||
SField const sfInvalid(access, -1);
|
||||
@@ -80,6 +89,8 @@ SField const sfIndex(access, STI_UINT256, 258, "index");
|
||||
|
||||
#include <xrpl/protocol/detail/sfields.macro>
|
||||
|
||||
#undef INTERPRETED_SFIELD
|
||||
#pragma pop_macro("INTERPRETED_SFIELD")
|
||||
#undef TYPED_SFIELD
|
||||
#pragma pop_macro("TYPED_SFIELD")
|
||||
#undef UNTYPED_SFIELD
|
||||
|
||||
@@ -686,7 +686,10 @@ STObject
|
||||
STObject::getFieldObject(SField const& field) const
|
||||
{
|
||||
STObject const empty{field};
|
||||
return getFieldByConstRef<STObject>(field, empty);
|
||||
auto ret = getFieldByConstRef<STObject>(field, empty);
|
||||
if (ret != empty)
|
||||
ret.applyTemplateFromSField(field);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const STArray&
|
||||
|
||||
@@ -75,7 +75,14 @@ class LoanBroker_test : public beast::unit_test::suite
|
||||
env(set(alice, keylet.key), fee(increment), ter(temDISABLED));
|
||||
auto const brokerKeylet =
|
||||
keylet::loanbroker(alice.id(), env.seq(alice));
|
||||
// LoanBrokerDelete is disabled, too.
|
||||
// Other LoanBroker transactions are disabled, too.
|
||||
// 1. LoanBrokerCoverDeposit
|
||||
env(coverDeposit(alice, brokerKeylet.key, asset(1000)),
|
||||
ter(temDISABLED));
|
||||
// 2. LoanBrokerCoverWithdraw
|
||||
env(coverWithdraw(alice, brokerKeylet.key, asset(1000)),
|
||||
ter(temDISABLED));
|
||||
// 3. LoanBrokerDelete
|
||||
env(del(alice, brokerKeylet.key), ter(temDISABLED));
|
||||
};
|
||||
failAll(all - featureMPTokensV1);
|
||||
@@ -421,7 +428,7 @@ class LoanBroker_test : public beast::unit_test::suite
|
||||
ter(tecNO_PERMISSION));
|
||||
// sfManagementFeeRate: too big
|
||||
env(set(evan, vault.vaultID),
|
||||
managementFeeRate(maxManagementFeeRate + 1),
|
||||
managementFeeRate(maxManagementFeeRate + TenthBips16(10)),
|
||||
fee(increment),
|
||||
ter(temINVALID));
|
||||
// sfCoverRateMinimum: good value, bad account
|
||||
@@ -555,10 +562,10 @@ class LoanBroker_test : public beast::unit_test::suite
|
||||
return env.jt(
|
||||
jv,
|
||||
data(testData),
|
||||
managementFeeRate(123),
|
||||
managementFeeRate(TenthBips16(123)),
|
||||
debtMaximum(Number(9)),
|
||||
coverRateMinimum(100),
|
||||
coverRateLiquidation(200));
|
||||
coverRateMinimum(TenthBips32(100)),
|
||||
coverRateLiquidation(TenthBips32(200)));
|
||||
},
|
||||
[&](SLE::const_ref broker) {
|
||||
// Extra checks
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
*/
|
||||
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/FeeUnits.h>
|
||||
#include <xrpl/protocol/SystemParameters.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class feeunits_test : public beast::unit_test::suite
|
||||
class units_test : public beast::unit_test::suite
|
||||
{
|
||||
private:
|
||||
void
|
||||
@@ -35,16 +35,16 @@ private:
|
||||
XRPAmount x{100};
|
||||
BEAST_EXPECT(x.drops() == 100);
|
||||
BEAST_EXPECT(
|
||||
(std::is_same_v<decltype(x)::unit_type, feeunit::dropTag>));
|
||||
(std::is_same_v<decltype(x)::unit_type, unit::dropTag>));
|
||||
auto y = 4u * x;
|
||||
BEAST_EXPECT(y.value() == 400);
|
||||
BEAST_EXPECT(
|
||||
(std::is_same_v<decltype(y)::unit_type, feeunit::dropTag>));
|
||||
(std::is_same_v<decltype(y)::unit_type, unit::dropTag>));
|
||||
|
||||
auto z = 4 * y;
|
||||
BEAST_EXPECT(z.value() == 1600);
|
||||
BEAST_EXPECT(
|
||||
(std::is_same_v<decltype(z)::unit_type, feeunit::dropTag>));
|
||||
(std::is_same_v<decltype(z)::unit_type, unit::dropTag>));
|
||||
|
||||
FeeLevel32 f{10};
|
||||
FeeLevel32 baseFee{100};
|
||||
@@ -55,7 +55,7 @@ private:
|
||||
BEAST_EXPECT(drops.value() == 1000);
|
||||
BEAST_EXPECT((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>::unit_type,
|
||||
feeunit::dropTag>));
|
||||
unit::dropTag>));
|
||||
|
||||
BEAST_EXPECT((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>,
|
||||
@@ -65,11 +65,11 @@ private:
|
||||
XRPAmount x{100};
|
||||
BEAST_EXPECT(x.value() == 100);
|
||||
BEAST_EXPECT(
|
||||
(std::is_same_v<decltype(x)::unit_type, feeunit::dropTag>));
|
||||
(std::is_same_v<decltype(x)::unit_type, unit::dropTag>));
|
||||
auto y = 4u * x;
|
||||
BEAST_EXPECT(y.value() == 400);
|
||||
BEAST_EXPECT(
|
||||
(std::is_same_v<decltype(y)::unit_type, feeunit::dropTag>));
|
||||
(std::is_same_v<decltype(y)::unit_type, unit::dropTag>));
|
||||
|
||||
FeeLevel64 f{10};
|
||||
FeeLevel64 baseFee{100};
|
||||
@@ -80,7 +80,7 @@ private:
|
||||
BEAST_EXPECT(drops.value() == 1000);
|
||||
BEAST_EXPECT((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>::unit_type,
|
||||
feeunit::dropTag>));
|
||||
unit::dropTag>));
|
||||
BEAST_EXPECT((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>,
|
||||
XRPAmount>));
|
||||
@@ -89,12 +89,12 @@ private:
|
||||
FeeLevel64 x{1024};
|
||||
BEAST_EXPECT(x.value() == 1024);
|
||||
BEAST_EXPECT(
|
||||
(std::is_same_v<decltype(x)::unit_type, feeunit::feelevelTag>));
|
||||
(std::is_same_v<decltype(x)::unit_type, unit::feelevelTag>));
|
||||
std::uint64_t m = 4;
|
||||
auto y = m * x;
|
||||
BEAST_EXPECT(y.value() == 4096);
|
||||
BEAST_EXPECT(
|
||||
(std::is_same_v<decltype(y)::unit_type, feeunit::feelevelTag>));
|
||||
(std::is_same_v<decltype(y)::unit_type, unit::feelevelTag>));
|
||||
|
||||
XRPAmount basefee{10};
|
||||
FeeLevel64 referencefee{256};
|
||||
@@ -105,7 +105,7 @@ private:
|
||||
BEAST_EXPECT(drops.value() == 40);
|
||||
BEAST_EXPECT((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>::unit_type,
|
||||
feeunit::dropTag>));
|
||||
unit::dropTag>));
|
||||
BEAST_EXPECT((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>,
|
||||
XRPAmount>));
|
||||
@@ -181,7 +181,7 @@ private:
|
||||
void
|
||||
testFunctions()
|
||||
{
|
||||
// Explicitly test every defined function for the TaggedFee class
|
||||
// Explicitly test every defined function for the ValueUnit class
|
||||
// since some of them are templated, but not used anywhere else.
|
||||
using FeeLevel32 = FeeLevel<std::uint32_t>;
|
||||
|
||||
@@ -191,8 +191,8 @@ private:
|
||||
return FeeLevel64{x};
|
||||
};
|
||||
|
||||
[[maybe_unused]]
|
||||
FeeLevel64 defaulted;
|
||||
(void)defaulted;
|
||||
FeeLevel64 test{0};
|
||||
BEAST_EXPECT(test.fee() == 0);
|
||||
|
||||
@@ -278,8 +278,8 @@ private:
|
||||
return FeeLevelDouble{x};
|
||||
};
|
||||
|
||||
[[maybe_unused]]
|
||||
FeeLevelDouble defaulted;
|
||||
(void)defaulted;
|
||||
FeeLevelDouble test{0};
|
||||
BEAST_EXPECT(test.fee() == 0);
|
||||
|
||||
@@ -371,7 +371,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(feeunits, ripple_basics, ripple);
|
||||
BEAST_DEFINE_TESTSUITE(units, ripple_basics, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -54,7 +54,9 @@ struct JTx
|
||||
bool fill_sig = true;
|
||||
bool fill_netid = true;
|
||||
std::shared_ptr<STTx const> stx;
|
||||
// TODO: Remove
|
||||
std::function<void(Env&, JTx&)> signer;
|
||||
std::vector<std::function<void(Env&, JTx&)>> signers;
|
||||
|
||||
JTx() = default;
|
||||
JTx(JTx const&) = default;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <vector>
|
||||
@@ -164,6 +165,37 @@ struct blobField : public JTxField<SF_VL, std::string>
|
||||
}
|
||||
};
|
||||
|
||||
template <class SField, class UnitTag, class ValueType>
|
||||
struct valueUnitField
|
||||
: public JTxField<SField, unit::ValueUnit<UnitTag, ValueType>, ValueType>
|
||||
{
|
||||
using SF = SField;
|
||||
using SV = unit::ValueUnit<UnitTag, ValueType>;
|
||||
using OV = ValueType;
|
||||
using base = JTxField<SF, SV, OV>;
|
||||
|
||||
static_assert(
|
||||
std::is_same_v<SV, typename SField::type::value_type::value_type>);
|
||||
static_assert(std::is_same_v<
|
||||
OV,
|
||||
typename SField::type::value_type::value_type::value_type>);
|
||||
|
||||
protected:
|
||||
using base::value_;
|
||||
|
||||
public:
|
||||
explicit valueUnitField(SF const& sfield, SV const& value)
|
||||
: JTxField(sfield, value)
|
||||
{
|
||||
}
|
||||
|
||||
OV
|
||||
value() const override
|
||||
{
|
||||
return value_.value();
|
||||
}
|
||||
};
|
||||
|
||||
template <class JTxField>
|
||||
struct JTxFieldWrapper
|
||||
{
|
||||
@@ -221,6 +253,13 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
class SField,
|
||||
class UnitTag = typename SField::type::value_type::unit_type,
|
||||
class ValueType = typename SField::type::value_type::value_type>
|
||||
using valueUnitWrapper =
|
||||
JTxFieldWrapper<valueUnitField<SField, UnitTag, ValueType>>;
|
||||
|
||||
template <class SField, class StoredValue = typename SField::type::value_type>
|
||||
using simpleField = JTxFieldWrapper<JTxField<SField, StoredValue>>;
|
||||
|
||||
@@ -627,17 +666,32 @@ auto const loanBrokerID = JTxFieldWrapper<uint256Field>(sfLoanBrokerID);
|
||||
|
||||
auto const data = JTxFieldWrapper<blobField>(sfData);
|
||||
|
||||
auto const managementFeeRate = simpleField<SF_UINT16>(sfManagementFeeRate);
|
||||
auto const managementFeeRate =
|
||||
valueUnitWrapper<SF_TENTHBIPS16>(sfManagementFeeRate);
|
||||
|
||||
auto const debtMaximum = simpleField<SF_NUMBER>(sfDebtMaximum);
|
||||
|
||||
auto const coverRateMinimum = simpleField<SF_UINT32>(sfCoverRateMinimum);
|
||||
auto const coverRateMinimum =
|
||||
valueUnitWrapper<SF_TENTHBIPS32>(sfCoverRateMinimum);
|
||||
|
||||
auto const coverRateLiquidation =
|
||||
simpleField<SF_UINT32>(sfCoverRateLiquidation);
|
||||
simpleField<SF_TENTHBIPS32>(sfCoverRateLiquidation);
|
||||
|
||||
} // namespace loanBroker
|
||||
|
||||
/* Loan */
|
||||
/******************************************************************************/
|
||||
namespace loan {
|
||||
|
||||
Json::Value
|
||||
set(AccountID const& account,
|
||||
uint256 loanBrokerID,
|
||||
Number principalRequested,
|
||||
NetClock::time_point const& startDate,
|
||||
uint32_t flags = 0);
|
||||
|
||||
}
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
#include <test/jtx/tags.h>
|
||||
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/protocol/FeeUnits.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
|
||||
@@ -470,6 +470,28 @@ coverWithdraw(
|
||||
|
||||
} // namespace loanBroker
|
||||
|
||||
/* Loan */
|
||||
/******************************************************************************/
|
||||
namespace loan {
|
||||
|
||||
Json::Value
|
||||
set(AccountID const& account,
|
||||
uint256 loanBrokerID,
|
||||
Number principalRequested,
|
||||
NetClock::time_point const& startDate,
|
||||
uint32_t flags)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[sfTransactionType] = jss::LoanSet;
|
||||
jv[sfAccount] = to_string(account);
|
||||
jv[sfLoanBrokerID] = to_string(loanBrokerID);
|
||||
jv[sfPrincipalRequested] = to_string(principalRequested);
|
||||
jv[sfFlags] = flags;
|
||||
jv[sfStartDate] = startDate.time_since_epoch().count();
|
||||
return jv;
|
||||
}
|
||||
|
||||
} // namespace loan
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
@@ -1382,7 +1382,11 @@ class Invariants_test : public beast::unit_test::suite
|
||||
sle->at(sfOwner) = sle->at(sfAccount);
|
||||
},
|
||||
[](SLE::pointer& sle) {
|
||||
sle->at(sfManagementFeeRate) += 1;
|
||||
// The operator overloads aren't playing nice with the
|
||||
// custom class, so just do it the hard way for now.
|
||||
auto value = sle->at(sfManagementFeeRate).value();
|
||||
++value;
|
||||
sle->at(sfManagementFeeRate) = value;
|
||||
},
|
||||
[](SLE::pointer& sle) { sle->at(sfCoverRateMinimum) += 1; },
|
||||
[](SLE::pointer& sle) {
|
||||
|
||||
@@ -805,7 +805,7 @@ class Simulate_test : public beast::unit_test::suite
|
||||
testTx(env, tx, testSimulation);
|
||||
|
||||
tx[sfSigningPubKey] = "";
|
||||
tx[sfTxnSignature] = "";
|
||||
tx[sfTxnSignature] = "x";
|
||||
tx[sfSequence] = env.seq(env.master);
|
||||
tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/protocol/FeeUnits.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
#include <xrpl/basics/mulDiv.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/FeeUnits.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/FeeUnits.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/SystemParameters.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
#include <xrpl/protocol/nftPageMask.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -125,6 +125,8 @@ LoanBrokerCoverDeposit::doApply()
|
||||
auto const amount = tx[sfAmount];
|
||||
|
||||
auto broker = view().peek(keylet::loanbroker(brokerID));
|
||||
if (!broker)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const brokerPseudoID = broker->at(sfAccount);
|
||||
|
||||
|
||||
@@ -138,6 +138,8 @@ LoanBrokerCoverWithdraw::doApply()
|
||||
auto const amount = tx[sfAmount];
|
||||
|
||||
auto broker = view().peek(keylet::loanbroker(brokerID));
|
||||
if (!broker)
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
auto const brokerPseudoID = broker->at(sfAccount);
|
||||
|
||||
|
||||
@@ -96,8 +96,12 @@ LoanBrokerDelete::doApply()
|
||||
|
||||
// Delete the loan broker
|
||||
auto broker = view().peek(keylet::loanbroker(brokerID));
|
||||
if (!broker)
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
auto const vaultID = broker->at(sfVaultID);
|
||||
auto const sleVault = view().read(keylet::vault(vaultID));
|
||||
if (!sleVault)
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
auto const vaultPseudoID = sleVault->at(sfAccount);
|
||||
auto const vaultAsset = sleVault->at(sfAsset);
|
||||
|
||||
|
||||
@@ -155,6 +155,8 @@ LoanBrokerSet::doApply()
|
||||
{
|
||||
// Modify an existing LoanBroker
|
||||
auto broker = view.peek(keylet::loanbroker(*brokerID));
|
||||
if (!broker)
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const data = tx[~sfData])
|
||||
broker->at(sfData) = *data;
|
||||
@@ -172,6 +174,8 @@ LoanBrokerSet::doApply()
|
||||
auto const sequence = tx.getSeqValue();
|
||||
|
||||
auto owner = view.peek(keylet::account(account_));
|
||||
if (!owner)
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
auto broker =
|
||||
std::make_shared<SLE>(keylet::loanbroker(account_, sequence));
|
||||
|
||||
@@ -180,10 +184,14 @@ LoanBrokerSet::doApply()
|
||||
if (auto const ter = dirLink(view, vaultPseudoID, broker, sfVaultNode))
|
||||
return ter;
|
||||
|
||||
/* We're already charging a higher fee, so we probably don't want to
|
||||
also charge a reserve.
|
||||
*
|
||||
adjustOwnerCount(view, owner, 1, j_);
|
||||
auto ownerCount = owner->at(sfOwnerCount);
|
||||
if (mPriorBalance < view.fees().accountReserve(ownerCount))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
*/
|
||||
|
||||
auto maybePseudo = createPseudoAccount(
|
||||
view, broker->key(), PseudoAccountOwnerType::LoanBroker);
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -63,14 +64,9 @@ LoanSet::doPreflight(PreflightContext const& ctx)
|
||||
auto const& tx = ctx.tx;
|
||||
auto const counterPartySig = ctx.tx.getFieldObject(sfCounterpartySignature);
|
||||
|
||||
// Copied from preflight1
|
||||
// TODO: Refactor into a helper function?
|
||||
if (auto const spk = counterPartySig.getFieldVL(sfSigningPubKey);
|
||||
!spk.empty() && !publicKeyType(makeSlice(spk)))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "preflight1: invalid signing key";
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
if (auto const ret =
|
||||
ripple::detail::preflightCheckSigningKey(counterPartySig, ctx.j))
|
||||
return ret;
|
||||
|
||||
if (auto const data = tx[~sfData]; data && !data->empty() &&
|
||||
!validDataLength(tx[~sfData], maxDataPayloadLength))
|
||||
@@ -96,34 +92,9 @@ LoanSet::doPreflight(PreflightContext const& ctx)
|
||||
return temINVALID;
|
||||
|
||||
// Copied from preflight2
|
||||
// TODO: Refactor into a helper function?
|
||||
if (ctx.flags & tapDRY_RUN) // simulation
|
||||
{
|
||||
if (!ctx.tx.getSignature(counterPartySig).empty())
|
||||
{
|
||||
// NOTE: This code should never be hit because it's checked in the
|
||||
// `simulate` RPC
|
||||
return temINVALID; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (!counterPartySig.isFieldPresent(sfSigners))
|
||||
{
|
||||
// no signers, no signature - a valid simulation
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
for (auto const& signer : counterPartySig.getFieldArray(sfSigners))
|
||||
{
|
||||
if (signer.isFieldPresent(sfTxnSignature) &&
|
||||
!signer[sfTxnSignature].empty())
|
||||
{
|
||||
// NOTE: This code should never be hit because it's
|
||||
// checked in the `simulate` RPC
|
||||
return temINVALID; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
if (auto const ret = ripple::detail::preflightCheckSimulateKeys(
|
||||
ctx.flags, counterPartySig, ctx.j))
|
||||
return *ret;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -152,7 +123,6 @@ LoanSet::checkSign(PreclaimContext const& ctx)
|
||||
auto const counterSig = ctx.tx.getFieldObject(sfCounterpartySignature);
|
||||
return Transactor::checkSign(ctx, *counterSigner, counterSig);
|
||||
}
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
LoanSet::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
@@ -176,120 +146,247 @@ LoanSet::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
TER
|
||||
LoanSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
return temDISABLED;
|
||||
|
||||
auto const& tx = ctx.tx;
|
||||
|
||||
if (auto const startDate(tx[sfStartDate]); hasExpired(ctx.view, startDate))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Start date is in the past.";
|
||||
return tecEXPIRED;
|
||||
}
|
||||
|
||||
auto const account = tx[sfAccount];
|
||||
if (auto const ID = tx[~sfLoanID])
|
||||
auto const brokerID = tx[sfLoanBrokerID];
|
||||
|
||||
auto const brokerSle = ctx.view.read(keylet::loanbroker(brokerID));
|
||||
if (!brokerSle)
|
||||
{
|
||||
auto const sle = ctx.view.read(keylet::loan(*ID));
|
||||
if (!sle)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Loan does not exist.";
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
if (tx[sfVaultID] != sle->at(sfVaultID))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Can not change VaultID on an existing Loan.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
if (account != sle->at(sfOwner))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Account is not the owner of the Loan.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
else
|
||||
auto const brokerOwner = brokerSle->at(sfOwner);
|
||||
if (!tx.isFieldPresent(sfCounterparty) && account != brokerOwner)
|
||||
{
|
||||
auto const vaultID = tx[sfVaultID];
|
||||
auto const sleVault = ctx.view.read(keylet::vault(vaultID));
|
||||
if (!sleVault)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Vault does not exist.";
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
if (account != sleVault->at(sfOwner))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Account is not the owner of the Vault.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Counterparty is not the owner of the LoanBroker.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
|
||||
auto const borrower = counterparty == brokerOwner ? account : counterparty;
|
||||
if (account != brokerOwner && counterparty != brokerOwner)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Neither Account nor Counterparty are the owner "
|
||||
"of the LoanBroker.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (auto const borrowerSle = ctx.view.read(keylet::account(borrower));
|
||||
!borrowerSle)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Borrower does not exist.";
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
|
||||
auto const brokerPseudo = brokerSle->at(sfAccount);
|
||||
auto const vault = ctx.view.read(keylet::vault(brokerSle->at(sfVaultID)));
|
||||
if (!vault)
|
||||
// Should be impossible
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
auto const asset = vault->at(sfAsset);
|
||||
if (isFrozen(ctx.view, brokerOwner, asset) ||
|
||||
isFrozen(ctx.view, brokerPseudo, asset))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "One of the affected accounts is frozen.";
|
||||
return asset.holds<Issue>() ? tecFROZEN : tecLOCKED;
|
||||
}
|
||||
if (asset.holds<Issue>())
|
||||
{
|
||||
auto const issue = asset.get<Issue>();
|
||||
if (isDeepFrozen(ctx.view, borrower, issue.currency, issue.account))
|
||||
return tecFROZEN;
|
||||
}
|
||||
auto const principalRequested = tx[sfPrincipalRequested];
|
||||
if (auto const assetsAvailable = vault->at(sfAssetsAvailable);
|
||||
assetsAvailable < principalRequested)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Insufficient assets available in the Vault to fund the loan.";
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
auto const debtTotal = brokerSle->at(sfDebtTotal);
|
||||
if (brokerSle->at(sfDebtMaximum) < debtTotal + principalRequested)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Loan would exceed the maximum debt limit of the LoanBroker.";
|
||||
return tecLIMIT_EXCEEDED;
|
||||
}
|
||||
if (brokerSle->at(sfCoverAvailable) <
|
||||
tenthBipsOfValue(
|
||||
debtTotal + principalRequested, brokerSle->at(sfCoverRateMinimum)))
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Insufficient first-loss capital to cover the loan.";
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
LoanSet::doApply()
|
||||
{
|
||||
return temDISABLED;
|
||||
|
||||
auto const& tx = ctx_.tx;
|
||||
auto& view = ctx_.view();
|
||||
|
||||
#if 0
|
||||
if (auto const ID = tx[~sfLoanID])
|
||||
auto const brokerID = tx[sfLoanBrokerID];
|
||||
|
||||
auto const brokerSle = view.peek(keylet::loanbroker(brokerID));
|
||||
if (!brokerSle)
|
||||
tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
auto const brokerOwner = brokerSle->at(sfOwner);
|
||||
auto const brokerOwnerSle = view.peek(keylet::account(brokerOwner));
|
||||
|
||||
auto const vaultSle = view.peek(keylet ::vault(brokerSle->at(sfVaultID)));
|
||||
if (!vaultSle)
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
auto const vaultPseudo = vaultSle->at(sfAccount);
|
||||
auto const vaultAsset = vaultSle->at(sfAsset);
|
||||
|
||||
auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
|
||||
auto const borrower = counterparty == brokerOwner ? account_ : counterparty;
|
||||
auto const borrowerSle = view.peek(keylet::account(borrower));
|
||||
if (!borrowerSle)
|
||||
{
|
||||
// Modify an existing Loan
|
||||
auto loan = view.peek(keylet::loan(*ID));
|
||||
|
||||
if (auto const data = tx[~sfData])
|
||||
loan->at(sfData) = *data;
|
||||
if (auto const debtMax = tx[~sfDebtMaximum])
|
||||
loan->at(sfDebtMaximum) = *debtMax;
|
||||
|
||||
view.update();
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
else
|
||||
|
||||
auto const brokerPseudo = brokerSle->at(sfAccount);
|
||||
auto const brokerPseudoSle = view.peek(keylet::account(brokerPseudo));
|
||||
auto const principalRequested = tx[sfPrincipalRequested];
|
||||
auto const interestRate = tx[~sfInterestRate].value_or(TenthBips32(0));
|
||||
auto const originationFee = tx[~sfLoanOriginationFee];
|
||||
auto const loanAssetsAvailable =
|
||||
principalRequested - originationFee.value_or(Number{});
|
||||
|
||||
adjustOwnerCount(view, borrowerSle, 1, j_);
|
||||
auto ownerCount = borrowerSle->at(sfOwnerCount);
|
||||
if (mPriorBalance < view.fees().accountReserve(ownerCount))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Create a holding for the borrower if one does not already exist.
|
||||
|
||||
// Account for the origination fee using two payments
|
||||
//
|
||||
// 1. Transfer (principalRequested - originationFee) from vault
|
||||
// pseudo-account to LoanBroker pseudo-account.
|
||||
//
|
||||
// Create the holding if it doesn't already exist
|
||||
if (auto const ter = addEmptyHolding(
|
||||
view,
|
||||
brokerPseudo,
|
||||
brokerPseudoSle->at(sfBalance).value().xrp(),
|
||||
vaultAsset,
|
||||
j_);
|
||||
ter != tesSUCCESS && ter != tecDUPLICATE)
|
||||
// ignore tecDUPLICATE. That means the holding already exists, and is
|
||||
// fine here
|
||||
return ter;
|
||||
if (auto const ter = accountSend(
|
||||
view,
|
||||
vaultPseudo,
|
||||
brokerPseudo,
|
||||
STAmount{vaultAsset, loanAssetsAvailable},
|
||||
j_,
|
||||
WaiveTransferFee::Yes))
|
||||
return ter;
|
||||
// 2. Transfer originationFee, if any, from vault pseudo-account to
|
||||
// LoanBroker owner.
|
||||
if (originationFee)
|
||||
{
|
||||
// Create a new Loan pointing back to the given Vault
|
||||
auto const vaultID = tx[sfVaultID];
|
||||
auto const sleVault = view.read(keylet::vault(vaultID));
|
||||
auto const vaultPseudoID = sleVault->at(sfAccount);
|
||||
auto const sequence = tx.getSeqValue();
|
||||
|
||||
auto owner = view.peek(keylet::account(account_));
|
||||
auto loan = std::make_shared<SLE>(keylet::loan(account_, sequence));
|
||||
|
||||
if (auto const ter = dirLink(view, account_, ))
|
||||
// Create the holding if it doesn't already exist
|
||||
if (auto const ter = addEmptyHolding(
|
||||
view,
|
||||
brokerOwner,
|
||||
brokerOwnerSle->at(sfBalance).value().xrp(),
|
||||
vaultAsset,
|
||||
j_);
|
||||
ter != tesSUCCESS && ter != tecDUPLICATE)
|
||||
// ignore tecDUPLICATE. That means the holding already exists, and
|
||||
// is fine here
|
||||
return ter;
|
||||
if (auto const ter = dirLink(view, vaultPseudoID, , sfVaultNode))
|
||||
if (auto const ter = accountSend(
|
||||
view,
|
||||
vaultPseudo,
|
||||
brokerOwner,
|
||||
STAmount{vaultAsset, *originationFee},
|
||||
j_,
|
||||
WaiveTransferFee::Yes))
|
||||
return ter;
|
||||
|
||||
adjustOwnerCount(view, owner, 1, j_);
|
||||
auto ownerCount = owner->at(sfOwnerCount);
|
||||
if (mPriorBalance < view.fees().accountReserve(ownerCount))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
auto maybePseudo =
|
||||
createPseudoAccount(view, loan->key(), PseudoAccountOwnerType::Loan);
|
||||
if (!maybePseudo)
|
||||
return maybePseudo.error();
|
||||
auto& pseudo = *maybePseudo;
|
||||
auto pseudoId = pseudo->at(sfAccount);
|
||||
|
||||
if (auto ter = addEmptyHolding(
|
||||
view, pseudoId, mPriorBalance, sleVault->at(sfAsset), j_))
|
||||
return ter;
|
||||
|
||||
// Initialize data fields:
|
||||
loan->at(sfSequence) = sequence;
|
||||
loan->at(sfVaultID) = vaultID;
|
||||
loan->at(sfOwner) = account_;
|
||||
loan->at(sfAccount) = pseudoId;
|
||||
if (auto const data = tx[~sfData])
|
||||
loan->at(sfData) = *data;
|
||||
if (auto const rate = tx[~sfManagementFeeRate])
|
||||
loan->at(sfManagementFeeRate) = *rate;
|
||||
if (auto const debtMax = tx[~sfDebtMaximum])
|
||||
loan->at(sfDebtMaximum) = *debtMax;
|
||||
if (auto const coverMin = tx[~sfCoverRateMinimum])
|
||||
loan->at(sfCoverRateMinimum) = *coverMin;
|
||||
if (auto const coverLiq = tx[~sfCoverRateLiquidation])
|
||||
loan->at(sfCoverRateLiquidation) = *coverLiq;
|
||||
|
||||
view.insert(loan);
|
||||
}
|
||||
#endif
|
||||
|
||||
auto const managementFeeRate = brokerSle->at(sfManagementFeeRate);
|
||||
auto const loanInterest =
|
||||
tenthBipsOfValue(principalRequested, interestRate);
|
||||
auto const loanInterestToVault = loanInterest -
|
||||
tenthBipsOfValue(loanInterest, managementFeeRate.value());
|
||||
auto const loanSequence = tx.getSeqValue();
|
||||
auto const startDate = tx[sfStartDate];
|
||||
auto const paymentInterval =
|
||||
tx[~sfPaymentInterval].value_or(defaultPaymentInterval);
|
||||
|
||||
// Create the loan
|
||||
auto loan =
|
||||
std::make_shared<SLE>(keylet::loan(borrower, brokerID, loanSequence));
|
||||
|
||||
// Prevent copy/paste errors
|
||||
auto setLoanField = [&loan, &tx](auto const& field) {
|
||||
loan->at(field) = tx[field];
|
||||
};
|
||||
|
||||
// Set required tx fields and pre-computed fields
|
||||
loan->at(sfPrincipalRequested) = principalRequested;
|
||||
loan->at(sfStartDate) = startDate;
|
||||
loan->at(sfSequence) = loanSequence;
|
||||
loan->at(sfLoanBrokerID) = brokerID;
|
||||
loan->at(sfBorrower) = borrower;
|
||||
loan->at(~sfLoanOriginationFee) = originationFee;
|
||||
loan->at(sfPaymentInterval) = paymentInterval;
|
||||
// Set all other transaction fields directly from the transaction
|
||||
if (tx.isFlag(tfLoanOverpayment))
|
||||
loan->setFlag(lsfLoanOverpayment);
|
||||
setLoanField(~sfData);
|
||||
setLoanField(~sfLoanServiceFee);
|
||||
setLoanField(~sfLatePaymentFee);
|
||||
setLoanField(~sfClosePaymentFee);
|
||||
setLoanField(~sfOverpaymentFee);
|
||||
setLoanField(~sfInterestRate);
|
||||
setLoanField(~sfLateInterestRate);
|
||||
setLoanField(~sfCloseInterestRate);
|
||||
setLoanField(~sfOverpaymentInterestRate);
|
||||
loan->at(sfGracePeriod) = tx[~sfGracePeriod].value_or(defaultGracePeriod);
|
||||
// Set dynamic fields to their initial values
|
||||
loan->at(sfPreviousPaymentDate) = 0;
|
||||
loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
|
||||
loan->at(sfPaymentRemaining) =
|
||||
tx[~sfPaymentTotal].value_or(defaultPaymentTotal);
|
||||
loan->at(sfAssetsAvailable) = loanAssetsAvailable;
|
||||
loan->at(sfPrincipalOutstanding) = principalRequested;
|
||||
|
||||
// Update the balances in the vault
|
||||
vaultSle->at(sfAssetsAvailable) -= principalRequested;
|
||||
vaultSle->at(sfAssetsTotal) += loanInterestToVault;
|
||||
view.update(vaultSle);
|
||||
|
||||
// Update the balances in the loan broker
|
||||
brokerSle->at(sfDebtTotal) += principalRequested + loanInterestToVault;
|
||||
adjustOwnerCount(view, brokerSle, 1, j_);
|
||||
|
||||
if (auto const ter = dirLink(view, brokerPseudo, loan, sfLoanBrokerNode))
|
||||
return ter;
|
||||
// Borrower is effectively the owner of the loan
|
||||
if (auto const ter = dirLink(view, borrower, loan, sfOwnerNode))
|
||||
return ter;
|
||||
|
||||
view.update(brokerSle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -87,6 +87,22 @@ preflight0(PreflightContext const& ctx, std::uint32_t flagMask)
|
||||
|
||||
namespace detail {
|
||||
|
||||
/** Checks the validity of the transactor signing key.
|
||||
*
|
||||
* Normally called from preflight1.
|
||||
*/
|
||||
NotTEC
|
||||
preflightCheckSigningKey(STObject const& sigObject, beast::Journal j)
|
||||
{
|
||||
if (auto const spk = sigObject.getFieldVL(sfSigningPubKey);
|
||||
!spk.empty() && !publicKeyType(makeSlice(spk)))
|
||||
{
|
||||
JLOG(j.debug()) << "preflightCheckSigningKey: invalid signing key";
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/** Performs early sanity checks on the account and fee fields */
|
||||
NotTEC
|
||||
preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
|
||||
@@ -118,13 +134,8 @@ preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
|
||||
return temBAD_FEE;
|
||||
}
|
||||
|
||||
auto const spk = ctx.tx.getSigningPubKey();
|
||||
|
||||
if (!spk.empty() && !publicKeyType(makeSlice(spk)))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "preflight1: invalid signing key";
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
if (auto const ret = preflightCheckSigningKey(ctx.tx, ctx.j))
|
||||
return ret;
|
||||
|
||||
// An AccountTxnID field constrains transaction ordering more than the
|
||||
// Sequence field. Tickets, on the other hand, reduce ordering
|
||||
@@ -139,26 +150,28 @@ preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/** Checks whether the signature appears valid */
|
||||
NotTEC
|
||||
preflight2(PreflightContext const& ctx)
|
||||
std::optional<NotTEC>
|
||||
preflightCheckSimulateKeys(
|
||||
ApplyFlags flags,
|
||||
STObject const& sigObject,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (ctx.flags & tapDRY_RUN) // simulation
|
||||
if (flags & tapDRY_RUN) // simulation
|
||||
{
|
||||
if (!ctx.tx.getSignature().empty())
|
||||
if (!sigObject.getFieldVL(sfTxnSignature).empty())
|
||||
{
|
||||
// NOTE: This code should never be hit because it's checked in the
|
||||
// `simulate` RPC
|
||||
return temINVALID; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (!ctx.tx.isFieldPresent(sfSigners))
|
||||
if (!sigObject.isFieldPresent(sfSigners))
|
||||
{
|
||||
// no signers, no signature - a valid simulation
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
for (auto const& signer : ctx.tx.getFieldArray(sfSigners))
|
||||
for (auto const& signer : sigObject.getFieldArray(sfSigners))
|
||||
{
|
||||
if (signer.isFieldPresent(sfTxnSignature) &&
|
||||
!signer[sfTxnSignature].empty())
|
||||
@@ -170,6 +183,17 @@ preflight2(PreflightContext const& ctx)
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/** Checks whether the signature appears valid */
|
||||
NotTEC
|
||||
preflight2(PreflightContext const& ctx)
|
||||
{
|
||||
if (auto const ret = preflightCheckSimulateKeys(ctx.flags, ctx.tx, ctx.j))
|
||||
// Skips following checks if the transaction is being simulated,
|
||||
// regardless of success or failure
|
||||
return *ret;
|
||||
|
||||
auto const sigValid = checkValidity(
|
||||
ctx.app.getHashRouter(), ctx.tx, ctx.rules, ctx.app.config());
|
||||
|
||||
@@ -262,6 +262,14 @@ NotTEC
|
||||
preflight0(PreflightContext const& ctx, std::uint32_t flagMask);
|
||||
|
||||
namespace detail {
|
||||
|
||||
/** Checks the validity of the transactor signing key.
|
||||
*
|
||||
* Normally called from preflight1 with ctx.tx.
|
||||
*/
|
||||
NotTEC
|
||||
preflightCheckSigningKey(STObject const& sigObject, beast::Journal j);
|
||||
|
||||
/** Performs early sanity checks on the account and fee fields.
|
||||
|
||||
(And passes flagMask to preflight0)
|
||||
@@ -269,6 +277,16 @@ namespace detail {
|
||||
NotTEC
|
||||
preflight1(PreflightContext const& ctx, std::uint32_t flagMask);
|
||||
|
||||
/** Checks the special signing key state needed for simulation
|
||||
*
|
||||
* Normally called from preflight2 with ctx.tx.
|
||||
*/
|
||||
std::optional<NotTEC>
|
||||
preflightCheckSimulateKeys(
|
||||
ApplyFlags flags,
|
||||
STObject const& sigObject,
|
||||
beast::Journal j);
|
||||
|
||||
/** Checks whether the signature appears valid */
|
||||
NotTEC
|
||||
preflight2(PreflightContext const& ctx);
|
||||
|
||||
Reference in New Issue
Block a user