Add single asset vault (XLS-65d) (#5224)

- Specification: XRPLF/XRPL-Standards#239
- Amendment: `SingleAssetVault`
- Implements a vault feature used to store a fungible asset (XRP, IOU, or MPT, but not NFT) and to receive shares in the vault (an MPT) in exchange.
- A vault can be private or public.
- A private vault can use permissioned domains, subject to the `PermissionedDomains` amendment.
- Shares can be exchanged back into asset with `VaultWithdraw`.
- Permissions on the asset in the vault are transitively applied on shares in the vault.
- Issuer of the asset in the vault can clawback with `VaultClawback`.
- Extended `MPTokenIssuance` with `DomainID`, used by the permissioned domain on the vault shares.

Co-authored-by: John Freeman <jfreeman08@gmail.com>
This commit is contained in:
Bronek Kozicki
2025-05-20 19:06:41 +01:00
committed by GitHub
parent dd62cfcc22
commit e514de76ed
93 changed files with 7257 additions and 385 deletions

View File

@@ -38,6 +38,7 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/UintTypes.h>
@@ -309,6 +310,7 @@ STAmount::xrp() const
"Cannot return non-native STAmount as XRPAmount");
auto drops = static_cast<XRPAmount::value_type>(mValue);
XRPL_ASSERT(mOffset == 0, "ripple::STAmount::xrp : amount is canonical");
if (mIsNegative)
drops = -drops;
@@ -338,6 +340,7 @@ STAmount::mpt() const
Throw<std::logic_error>("Cannot return STAmount as MPTAmount");
auto value = static_cast<MPTAmount::value_type>(mValue);
XRPL_ASSERT(mOffset == 0, "ripple::STAmount::mpt : amount is canonical");
if (mIsNegative)
value = -value;
@@ -865,75 +868,16 @@ amountFromQuality(std::uint64_t rate)
STAmount
amountFromString(Asset const& asset, std::string const& amount)
{
static boost::regex const reNumber(
"^" // the beginning of the string
"([-+]?)" // (optional) + or - character
"(0|[1-9][0-9]*)" // a number (no leading zeroes, unless 0)
"(\\.([0-9]+))?" // (optional) period followed by any number
"([eE]([+-]?)([0-9]+))?" // (optional) E, optional + or -, any number
"$",
boost::regex_constants::optimize);
boost::smatch match;
if (!boost::regex_match(amount, match, reNumber))
Throw<std::runtime_error>("Number '" + amount + "' is not valid");
// Match fields:
// 0 = whole input
// 1 = sign
// 2 = integer portion
// 3 = whole fraction (with '.')
// 4 = fraction (without '.')
// 5 = whole exponent (with 'e')
// 6 = exponent sign
// 7 = exponent number
// CHECKME: Why 32? Shouldn't this be 16?
if ((match[2].length() + match[4].length()) > 32)
Throw<std::runtime_error>("Number '" + amount + "' is overlong");
bool negative = (match[1].matched && (match[1] == "-"));
// Can't specify XRP or MPT using fractional representation
if ((asset.native() || asset.holds<MPTIssue>()) && match[3].matched)
auto const parts = partsFromString(amount);
if ((asset.native() || asset.holds<MPTIssue>()) && parts.exponent < 0)
Throw<std::runtime_error>(
"XRP and MPT must be specified as integral amount.");
std::uint64_t mantissa;
int exponent;
if (!match[4].matched) // integer only
{
mantissa =
beast::lexicalCastThrow<std::uint64_t>(std::string(match[2]));
exponent = 0;
}
else
{
// integer and fraction
mantissa = beast::lexicalCastThrow<std::uint64_t>(match[2] + match[4]);
exponent = -(match[4].length());
}
if (match[5].matched)
{
// we have an exponent
if (match[6].matched && (match[6] == "-"))
exponent -= beast::lexicalCastThrow<int>(std::string(match[7]));
else
exponent += beast::lexicalCastThrow<int>(std::string(match[7]));
}
return {asset, mantissa, exponent, negative};
return {asset, parts.mantissa, parts.exponent, parts.negative};
}
STAmount
amountFromJson(SField const& name, Json::Value const& v)
{
STAmount::mantissa_type mantissa = 0;
STAmount::exponent_type exponent = 0;
bool negative = false;
Asset asset;
Json::Value value;
@@ -1025,36 +969,38 @@ amountFromJson(SField const& name, Json::Value const& v)
}
}
NumberParts parts;
if (value.isInt())
{
if (value.asInt() >= 0)
{
mantissa = value.asInt();
parts.mantissa = value.asInt();
}
else
{
mantissa = -value.asInt();
negative = true;
parts.mantissa = -value.asInt();
parts.negative = true;
}
}
else if (value.isUInt())
{
mantissa = v.asUInt();
parts.mantissa = v.asUInt();
}
else if (value.isString())
{
auto const ret = amountFromString(asset, value.asString());
mantissa = ret.mantissa();
exponent = ret.exponent();
negative = ret.negative();
parts = partsFromString(value.asString());
// Can't specify XRP or MPT using fractional representation
if ((asset.native() || asset.holds<MPTIssue>()) && parts.exponent < 0)
Throw<std::runtime_error>(
"XRP and MPT must be specified as integral amount.");
}
else
{
Throw<std::runtime_error>("invalid amount type");
}
return {name, asset, mantissa, exponent, negative};
return {name, asset, parts.mantissa, parts.exponent, parts.negative};
}
bool