mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-04 11:15:56 +00:00
- Added a new Invariant: `ValidPseudoAccounts` which checks that all pseudo-accounts behave consistently through creation and updates, and that no "real" accounts look like pseudo-accounts (which means they don't have a 0 sequence).
- `to_short_string(base_uint)`. Like `to_string`, but only returns the first 8 characters. (Similar to how a git commit ID can be abbreviated.) Used as a wrapped sink to prefix most transaction-related messages. More can be added later.
- `XRPL_ASSERT_PARTS`. Convenience wrapper for `XRPL_ASSERT`, which takes the `function` and `description` as separate parameters.
- `SField::sMD_PseudoAccount`. Metadata option for `SField` definitions to indicate that the field, if set in an `AccountRoot` indicates that account is a pseudo-account. Removes the need for hard-coded field lists all over the place. Added the flag to `AMMID` and `VaultID`.
- Added functionality to `SField` ctor to detect both code and name collisions using asserts. And require all SFields to have a name
- Convenience type aliases `STLedgerEntry::const_pointer` and `STLedgerEntry::const_ref`. (`SLE` is an alias to `STLedgerEntry`.)
- Generalized `feeunit.h` (`TaggedFee`) into `unit.h` (`ValueUnit`) and added new "BIPS"-related tags for future use. Also refactored the type restrictions to use Concepts.
- Restructured `transactions.macro` to do two big things
1. Include the `#include` directives for transactor header files directly in the macro file. Removes the need to update `applySteps.cpp` and the resulting conflicts.
2. Added a `privileges` parameter to the `TRANSACTION` macro, which specifies some of the operations a transaction is allowed to do. These `privileges` are enforced by invariant checks. Again, removed the need to update scattered lists of transaction types in various checks.
- Unit tests:
1. Moved more helper functions into `TestHelpers.h` and `.cpp`.
2. Cleaned up the namespaces to prevent / mitigate random collisions and ambiguous symbols, particularly in unity builds.
3. Generalized `Env::balance` to add support for `MPTIssue` and `Asset`.
4. Added a set of helper classes to simplify `Env` transaction parameter classes: `JTxField`, `JTxFieldWrapper`, and a bunch of classes derived or aliased from it. For an example of how awesome it is, check the changes `src/test/jtx/escrow.h` for how much simpler the definitions are for `finish_time`, `cancel_time`, `condition`, and `fulfillment`.
5. Generalized several of the amount-related helper classes to understand `Asset`s.
6. `env.balance` for an MPT issuer will return a negative number (or 0) for consistency with IOUs.
556 lines
15 KiB
C++
556 lines
15 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2019 Ripple Labs Inc.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#ifndef PROTOCOL_UNITS_H_INCLUDED
|
|
#define PROTOCOL_UNITS_H_INCLUDED
|
|
|
|
#include <xrpl/basics/safe_cast.h>
|
|
#include <xrpl/beast/utility/Zero.h>
|
|
#include <xrpl/beast/utility/instrumentation.h>
|
|
#include <xrpl/json/json_value.h>
|
|
|
|
#include <boost/multiprecision/cpp_int.hpp>
|
|
#include <boost/operators.hpp>
|
|
|
|
#include <iosfwd>
|
|
#include <limits>
|
|
#include <optional>
|
|
|
|
namespace ripple {
|
|
|
|
namespace unit {
|
|
|
|
/** "drops" are the smallest divisible amount of XRP. This is what most
|
|
of the code uses. */
|
|
struct dropTag;
|
|
/** "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 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;
|
|
|
|
// These names don't have to be too descriptive, because we're in the "unit"
|
|
// namespace.
|
|
|
|
template <class T>
|
|
concept Valid = std::is_class_v<T> && std::is_object_v<typename T::unit_type> &&
|
|
std::is_object_v<typename T::value_type>;
|
|
|
|
/** `Usable` is checked to ensure that only values with
|
|
known valid type tags can be used (sometimes transparently) in
|
|
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>
|
|
concept Usable = Valid<T> &&
|
|
(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, BipsTag> ||
|
|
std::is_same_v<typename T::unit_type, TenthBipsTag>);
|
|
|
|
template <class Other, class VU>
|
|
concept Compatible = Valid<VU> && std::is_arithmetic_v<Other> &&
|
|
std::is_arithmetic_v<typename VU::value_type> &&
|
|
std::is_convertible_v<Other, typename VU::value_type>;
|
|
|
|
template <class T>
|
|
concept Integral = std::is_integral_v<T>;
|
|
|
|
template <class VU>
|
|
concept IntegralValue = Integral<typename VU::value_type>;
|
|
|
|
template <class VU1, class VU2>
|
|
concept CastableValue = IntegralValue<VU1> && IntegralValue<VU2> &&
|
|
std::is_same_v<typename VU1::unit_type, typename VU2::unit_type>;
|
|
|
|
template <class UnitTag, class 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 value_;
|
|
|
|
public:
|
|
ValueUnit() = default;
|
|
constexpr ValueUnit(ValueUnit const& other) = default;
|
|
constexpr ValueUnit&
|
|
operator=(ValueUnit const& other) = default;
|
|
|
|
constexpr explicit ValueUnit(beast::Zero) : value_(0)
|
|
{
|
|
}
|
|
|
|
constexpr ValueUnit&
|
|
operator=(beast::Zero)
|
|
{
|
|
value_ = 0;
|
|
return *this;
|
|
}
|
|
|
|
constexpr explicit ValueUnit(value_type value) : value_(value)
|
|
{
|
|
}
|
|
|
|
constexpr ValueUnit&
|
|
operator=(value_type value)
|
|
{
|
|
value_ = value;
|
|
return *this;
|
|
}
|
|
|
|
/** Instances with the same unit, and a type that is
|
|
"safe" to convert to this one can be converted
|
|
implicitly */
|
|
template <Compatible<ValueUnit> Other>
|
|
constexpr ValueUnit(ValueUnit<unit_type, Other> const& value)
|
|
requires SafeToCast<Other, value_type>
|
|
: ValueUnit(safe_cast<value_type>(value.value()))
|
|
{
|
|
}
|
|
|
|
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 ValueUnit{value_ * rhs};
|
|
}
|
|
|
|
friend constexpr ValueUnit
|
|
operator*(value_type lhs, ValueUnit const& rhs)
|
|
{
|
|
// multiplication is commutative
|
|
return rhs * lhs;
|
|
}
|
|
|
|
constexpr value_type
|
|
operator/(ValueUnit const& rhs) const
|
|
{
|
|
return value_ / rhs.value_;
|
|
}
|
|
|
|
ValueUnit&
|
|
operator+=(ValueUnit const& other)
|
|
{
|
|
value_ += other.value();
|
|
return *this;
|
|
}
|
|
|
|
ValueUnit&
|
|
operator-=(ValueUnit const& other)
|
|
{
|
|
value_ -= other.value();
|
|
return *this;
|
|
}
|
|
|
|
ValueUnit&
|
|
operator++()
|
|
{
|
|
++value_;
|
|
return *this;
|
|
}
|
|
|
|
ValueUnit&
|
|
operator--()
|
|
{
|
|
--value_;
|
|
return *this;
|
|
}
|
|
|
|
ValueUnit&
|
|
operator*=(value_type const& rhs)
|
|
{
|
|
value_ *= rhs;
|
|
return *this;
|
|
}
|
|
|
|
ValueUnit&
|
|
operator/=(value_type const& rhs)
|
|
{
|
|
value_ /= rhs;
|
|
return *this;
|
|
}
|
|
|
|
template <Integral transparent = value_type>
|
|
ValueUnit&
|
|
operator%=(value_type const& rhs)
|
|
{
|
|
value_ %= rhs;
|
|
return *this;
|
|
}
|
|
|
|
ValueUnit
|
|
operator-() const
|
|
{
|
|
static_assert(
|
|
std::is_signed_v<T>, "- operator illegal on unsigned value types");
|
|
return ValueUnit{-value_};
|
|
}
|
|
|
|
constexpr bool
|
|
operator==(ValueUnit const& other) const
|
|
{
|
|
return value_ == other.value_;
|
|
}
|
|
|
|
template <Compatible<ValueUnit> Other>
|
|
constexpr bool
|
|
operator==(ValueUnit<unit_type, Other> const& other) const
|
|
{
|
|
return value_ == other.value();
|
|
}
|
|
|
|
constexpr bool
|
|
operator==(value_type other) const
|
|
{
|
|
return value_ == other;
|
|
}
|
|
|
|
template <Compatible<ValueUnit> Other>
|
|
constexpr bool
|
|
operator!=(ValueUnit<unit_type, Other> const& other) const
|
|
{
|
|
return !operator==(other);
|
|
}
|
|
|
|
constexpr bool
|
|
operator<(ValueUnit const& other) const
|
|
{
|
|
return value_ < other.value_;
|
|
}
|
|
|
|
/** Returns true if the amount is not zero */
|
|
explicit constexpr
|
|
operator bool() const noexcept
|
|
{
|
|
return value_ != 0;
|
|
}
|
|
|
|
/** Return the sign of the amount */
|
|
constexpr int
|
|
signum() const noexcept
|
|
{
|
|
return (value_ < 0) ? -1 : (value_ ? 1 : 0);
|
|
}
|
|
|
|
/** Returns the number of drops */
|
|
// TODO: Move this to a new class, maybe with the old "TaggedFee" name
|
|
constexpr value_type
|
|
fee() const
|
|
{
|
|
return value_;
|
|
}
|
|
|
|
template <class Other>
|
|
constexpr double
|
|
decimalFromReference(ValueUnit<unit_type, Other> reference) const
|
|
{
|
|
return static_cast<double>(value_) / reference.value();
|
|
}
|
|
|
|
// `Usable` 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.
|
|
Json::Value
|
|
jsonClipped() const
|
|
requires Usable<ValueUnit>
|
|
{
|
|
if constexpr (std::is_integral_v<value_type>)
|
|
{
|
|
using jsontype = std::conditional_t<
|
|
std::is_signed_v<value_type>,
|
|
Json::Int,
|
|
Json::UInt>;
|
|
|
|
constexpr auto min = std::numeric_limits<jsontype>::min();
|
|
constexpr auto max = std::numeric_limits<jsontype>::max();
|
|
|
|
if (value_ < min)
|
|
return min;
|
|
if (value_ > max)
|
|
return max;
|
|
return static_cast<jsontype>(value_);
|
|
}
|
|
else
|
|
{
|
|
return value_;
|
|
}
|
|
}
|
|
|
|
/** Returns the underlying value. Code SHOULD NOT call this
|
|
function unless the type has been abstracted away,
|
|
e.g. in a templated function.
|
|
*/
|
|
constexpr value_type
|
|
value() const
|
|
{
|
|
return value_;
|
|
}
|
|
|
|
friend std::istream&
|
|
operator>>(std::istream& s, ValueUnit& val)
|
|
{
|
|
s >> val.value_;
|
|
return s;
|
|
}
|
|
};
|
|
|
|
// 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, ValueUnit<UnitTag, T> const& q)
|
|
{
|
|
return os << q.value();
|
|
}
|
|
|
|
template <class UnitTag, class T>
|
|
std::string
|
|
to_string(ValueUnit<UnitTag, T> const& amount)
|
|
{
|
|
return std::to_string(amount.value());
|
|
}
|
|
|
|
template <class Source>
|
|
concept muldivSource = Valid<Source> &&
|
|
std::is_convertible_v<typename Source::value_type, std::uint64_t>;
|
|
|
|
template <class Dest>
|
|
concept muldivDest = muldivSource<Dest> && // Dest is also a source
|
|
std::is_convertible_v<std::uint64_t, typename Dest::value_type> &&
|
|
sizeof(typename Dest::value_type) >= sizeof(std::uint64_t);
|
|
|
|
template <class Source2, class Source1>
|
|
concept muldivSources = muldivSource<Source1> && muldivSource<Source2> &&
|
|
std::is_same_v<typename Source1::unit_type, typename Source2::unit_type>;
|
|
|
|
template <class Dest, class Source1, class Source2>
|
|
concept muldivable = muldivSources<Source1, Source2> && muldivDest<Dest>;
|
|
// Source and Dest can be the same by default
|
|
|
|
template <class Dest, class Source1, class Source2>
|
|
concept muldivCommutable = muldivable<Dest, Source1, Source2> &&
|
|
!std::is_same_v<typename Source1::unit_type, typename Dest::unit_type>;
|
|
|
|
template <class T>
|
|
ValueUnit<unitlessTag, T>
|
|
scalar(T value)
|
|
{
|
|
return ValueUnit<unitlessTag, T>{value};
|
|
}
|
|
|
|
template <class Source1, class Source2, unit::muldivable<Source1, Source2> Dest>
|
|
std::optional<Dest>
|
|
mulDivU(Source1 value, Dest mul, Source2 div)
|
|
{
|
|
// 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::unit::mulDivU : minimum value input");
|
|
XRPL_ASSERT(
|
|
mul.value() >= 0, "ripple::unit::mulDivU : minimum mul input");
|
|
XRPL_ASSERT(
|
|
div.value() > 0, "ripple::unit::mulDivU : minimum div input");
|
|
return std::nullopt;
|
|
}
|
|
|
|
using desttype = typename Dest::value_type;
|
|
constexpr auto max = std::numeric_limits<desttype>::max();
|
|
|
|
// Shortcuts, since these happen a lot in the real world
|
|
if (value == div)
|
|
return mul;
|
|
if (mul.value() == div.value())
|
|
{
|
|
if (value.value() > max)
|
|
return std::nullopt;
|
|
return Dest{static_cast<desttype>(value.value())};
|
|
}
|
|
|
|
using namespace boost::multiprecision;
|
|
|
|
uint128_t product;
|
|
product = multiply(
|
|
product,
|
|
static_cast<std::uint64_t>(value.value()),
|
|
static_cast<std::uint64_t>(mul.value()));
|
|
|
|
auto quotient = product / div.value();
|
|
|
|
if (quotient > max)
|
|
return std::nullopt;
|
|
|
|
return Dest{static_cast<desttype>(quotient)};
|
|
}
|
|
|
|
} // namespace unit
|
|
|
|
// Fee Levels
|
|
template <class 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, unit::muldivable<Source1, Source2> Dest>
|
|
std::optional<Dest>
|
|
mulDiv(Source1 value, Dest mul, Source2 div)
|
|
{
|
|
return unit::mulDivU(value, mul, div);
|
|
}
|
|
|
|
template <
|
|
class Source1,
|
|
class Source2,
|
|
unit::muldivCommutable<Source1, Source2> Dest>
|
|
std::optional<Dest>
|
|
mulDiv(Dest value, Source1 mul, Source2 div)
|
|
{
|
|
// Multiplication is commutative
|
|
return unit::mulDivU(mul, value, div);
|
|
}
|
|
|
|
template <unit::muldivDest 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 unit::mulDivU(unit::scalar(value), mul, unit::scalar(div));
|
|
}
|
|
|
|
template <unit::muldivDest Dest>
|
|
std::optional<Dest>
|
|
mulDiv(Dest value, std::uint64_t mul, std::uint64_t div)
|
|
{
|
|
// Multiplication is commutative
|
|
return mulDiv(mul, value, div);
|
|
}
|
|
|
|
template <unit::muldivSource Source1, unit::muldivSources<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 = unit::mulDivU(value, unit::scalar(mul), div);
|
|
|
|
if (!unitresult)
|
|
return std::nullopt;
|
|
|
|
return unitresult->value();
|
|
}
|
|
|
|
template <unit::muldivSource Source1, unit::muldivSources<Source1> Source2>
|
|
std::optional<std::uint64_t>
|
|
mulDiv(std::uint64_t value, Source1 mul, Source2 div)
|
|
{
|
|
// Multiplication is commutative
|
|
return mulDiv(mul, value, div);
|
|
}
|
|
|
|
template <unit::IntegralValue Dest, unit::CastableValue<Dest> Src>
|
|
constexpr Dest
|
|
safe_cast(Src s) noexcept
|
|
{
|
|
// Dest may not have an explicit value constructor
|
|
return Dest{safe_cast<typename Dest::value_type>(s.value())};
|
|
}
|
|
|
|
template <unit::IntegralValue Dest, unit::Integral Src>
|
|
constexpr Dest
|
|
safe_cast(Src s) noexcept
|
|
{
|
|
// Dest may not have an explicit value constructor
|
|
return Dest{safe_cast<typename Dest::value_type>(s)};
|
|
}
|
|
|
|
template <unit::IntegralValue Dest, unit::CastableValue<Dest> Src>
|
|
constexpr Dest
|
|
unsafe_cast(Src s) noexcept
|
|
{
|
|
// Dest may not have an explicit value constructor
|
|
return Dest{unsafe_cast<typename Dest::value_type>(s.value())};
|
|
}
|
|
|
|
template <unit::IntegralValue Dest, unit::Integral Src>
|
|
constexpr Dest
|
|
unsafe_cast(Src s) noexcept
|
|
{
|
|
// Dest may not have an explicit value constructor
|
|
return Dest{unsafe_cast<typename Dest::value_type>(s)};
|
|
}
|
|
|
|
} // namespace ripple
|
|
|
|
#endif // PROTOCOL_UNITS_H_INCLUDED
|