xrpld
Loading...
Searching...
No Matches
VaultHelpers.cpp
1#include <xrpl/ledger/helpers/VaultHelpers.h>
2
3#include <xrpl/basics/Number.h>
4#include <xrpl/beast/utility/Zero.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/ledger/ReadView.h>
7#include <xrpl/ledger/View.h>
8#include <xrpl/ledger/helpers/CredentialHelpers.h>
9#include <xrpl/protocol/AccountID.h>
10#include <xrpl/protocol/Indexes.h>
11#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
12#include <xrpl/protocol/Protocol.h>
13#include <xrpl/protocol/SField.h>
14#include <xrpl/protocol/STAmount.h>
15#include <xrpl/protocol/STLedgerEntry.h>
16#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
17#include <xrpl/protocol/STTx.h>
18#include <xrpl/protocol/TER.h>
19
20#include <cstdint>
21#include <expected>
22#include <optional>
23#include <utility>
24
25namespace xrpl {
26
27[[nodiscard]] std::optional<STAmount>
29{
30 XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesDeposit : non-negative assets");
31 XRPL_ASSERT(
32 assets.asset() == vault->at(sfAsset),
33 "xrpl::assetsToSharesDeposit : assets and vault match");
34 if (assets.negative() || assets.asset() != vault->at(sfAsset))
35 return std::nullopt; // LCOV_EXCL_LINE
36
37 Number const assetTotal = vault->at(sfAssetsTotal);
38 STAmount shares{vault->at(sfShareMPTID)};
39 if (assetTotal == 0)
40 {
41 return STAmount{
42 shares.asset(),
43 Number(assets.mantissa(), assets.exponent() + vault->at(sfScale)).truncate()};
44 }
45
46 Number const shareTotal = issuance->at(sfOutstandingAmount);
47 shares = ((shareTotal * assets) / assetTotal).truncate();
48 return shares;
49}
50
51[[nodiscard]] std::optional<STAmount>
53{
54 XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsDeposit : non-negative shares");
55 XRPL_ASSERT(
56 shares.asset() == vault->at(sfShareMPTID),
57 "xrpl::sharesToAssetsDeposit : shares and vault match");
58 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
59 return std::nullopt; // LCOV_EXCL_LINE
60
61 Number const assetTotal = vault->at(sfAssetsTotal);
62 STAmount assets{vault->at(sfAsset)};
63 if (assetTotal == 0)
64 {
65 return STAmount{
66 assets.asset(), shares.mantissa(), shares.exponent() - vault->at(sfScale), false};
67 }
68
69 Number const shareTotal = issuance->at(sfOutstandingAmount);
70 assets = (assetTotal * shares) / shareTotal;
71 return assets;
72}
73
74[[nodiscard]] std::expected<STAmount, TER>
76{
77 XRPL_ASSERT(
78 delta.asset() == vault->at(sfAsset),
79 "xrpl::clampToAssetsTotalScale : delta and vault asset match");
80
81 Asset const asset = vault->at(sfAsset);
82
83 STAmount magnitude = delta.negative() ? -delta : delta;
84 if (asset.integral())
85 {
86 return magnitude;
87 }
88 Number const assetsTotal = vault->at(sfAssetsTotal);
89
90 // Calculate the scale after applying the delta using ToNearest rounding.
91 // This aligns the delta with scale checks used by vault invariants.
92 int const postScale = [&] {
94 return scale(assetsTotal + delta, asset);
95 }();
96
97 STAmount actualDelta;
98 if (delta.negative())
99 {
100 // For withdrawals (debits), floor the magnitude to the target scale
101 // to ensure exact grid alignment without paying out extra assets.
102 actualDelta = roundToScale(magnitude, postScale, Number::RoundingMode::Downward);
103 }
104 else
105 {
106 // For deposits (credits), derive actualDelta from the floored posterior total.
107 // This prevents grid alignment issues from crediting the vault more than deposited.
108 //
109 // Sum using Downward rounding so intermediate precision doesn't round up
110 // and exceed the original requested amount.
111 Number const posterior = [&] {
113 return assetsTotal + magnitude;
114 }();
115
116 Number const roundedPosterior =
117 roundToAsset(asset, posterior, postScale, Number::RoundingMode::Downward);
118 actualDelta = STAmount{asset, roundedPosterior - assetsTotal};
119 }
120
121 XRPL_ASSERT(
122 abs(actualDelta) <= abs(delta),
123 "xrpl::clampToAssetsTotalScale : actual delta smaller or equal to calculated delta");
124
125 // Reject changes below scale precision (1 ULP) to prevent share balance changes
126 // without corresponding asset movements.
127 if (actualDelta <= beast::kZero)
129
130 return actualDelta;
131}
132
133[[nodiscard]] Number
135{
136 Number assetTotal = vault->at(sfAssetsTotal);
137 if (waive == WaiveUnrealizedLoss::No)
138 assetTotal -= vault->at(sfLossUnrealized);
139 return assetTotal;
140}
141
142[[nodiscard]] bool
143debitIsNonZeroDust(Asset const& asset, Number const& total, Number const& amount)
144{
145 if (amount == 0)
146 return false;
147 return STAmount{asset, total - amount} == STAmount{asset, total};
148}
149
150[[nodiscard]] std::optional<STAmount>
152 SLE::ConstRef vault,
153 SLE::ConstRef issuance,
154 STAmount const& assets,
155 TruncateShares truncate,
157{
158 XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesWithdraw : non-negative assets");
159 XRPL_ASSERT(
160 assets.asset() == vault->at(sfAsset),
161 "xrpl::assetsToSharesWithdraw : assets and vault match");
162 if (assets.negative() || assets.asset() != vault->at(sfAsset))
163 return std::nullopt; // LCOV_EXCL_LINE
164
165 Number const assetTotal = assetsTotalForWithdrawal(vault, waive);
166 STAmount shares{vault->at(sfShareMPTID)};
167 if (assetTotal == 0)
168 return shares;
169 Number const shareTotal = issuance->at(sfOutstandingAmount);
170 Number result = (shareTotal * assets) / assetTotal;
171 if (truncate == TruncateShares::Yes)
172 result = result.truncate();
173 shares = result;
174 return shares;
175}
176
177[[nodiscard]] std::optional<STAmount>
179 SLE::ConstRef vault,
180 SLE::ConstRef issuance,
181 STAmount const& shares,
183{
184 XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsWithdraw : non-negative shares");
185 XRPL_ASSERT(
186 shares.asset() == vault->at(sfShareMPTID),
187 "xrpl::sharesToAssetsWithdraw : shares and vault match");
188 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
189 return std::nullopt; // LCOV_EXCL_LINE
190
191 Number const assetTotal = assetsTotalForWithdrawal(vault, waive);
192 STAmount assets{vault->at(sfAsset)};
193 if (assetTotal == 0)
194 return assets;
195 Number const shareTotal = issuance->at(sfOutstandingAmount);
196 assets = (assetTotal * shares) / shareTotal;
197 return assets;
198}
199
200[[nodiscard]] bool
201isSoleShareholder(ReadView const& view, AccountID const& account, SLE::ConstRef issuance)
202{
203 XRPL_ASSERT(
204 issuance && issuance->getType() == ltMPTOKEN_ISSUANCE,
205 "xrpl::isSoleShareholder : valid issuance SLE");
206
207 std::uint64_t const outstanding = issuance->at(sfOutstandingAmount);
208 if (outstanding == 0)
209 return false;
210
211 auto const shareMPTID =
212 makeMptID(issuance->getFieldU32(sfSequence), issuance->getAccountID(sfIssuer));
213 auto const sleToken = view.read(keylet::mptoken(shareMPTID, account));
214 if (!sleToken)
215 return false; // LCOV_EXCL_LINE
216
217 return sleToken->getFieldU64(sfMPTAmount) == outstanding;
218}
219
220[[nodiscard]] VaultVersion
222{
223 XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::getVaultVersion : valid Vault sle");
224 if (!vault->isFieldPresent(sfLEVersion))
226
227 auto const version = vault->at(sfLEVersion);
228 if (version > std::to_underlying(VaultVersion::CashBasis))
229 {
230 // LCOV_EXCL_START
231 UNREACHABLE("xrpl::getVaultVersion : invalid vault version");
233 // LCOV_EXCL_STOP
234 }
235 return static_cast<VaultVersion>(version);
236}
237
238namespace {
239
240[[nodiscard]] VaultKind
241decodeVaultKind(std::optional<std::uint8_t> vaultKind)
242{
243 if (vaultKind && *vaultKind == std::to_underlying(VaultKind::ClosedEnded))
246}
247
248} // namespace
249
250[[nodiscard]] VaultKind
252{
253 XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::getVaultKind : valid Vault sle");
254 return decodeVaultKind(vault->at(~sfVaultKind));
255}
256
257[[nodiscard]] VaultKind
259{
260 return decodeVaultKind(tx[~sfVaultKind]);
261}
262
263[[nodiscard]] bool
265{
266 auto const kindField = tx[~sfVaultKind];
267 if (!kindField)
268 return true;
269 return *kindField == std::to_underlying(VaultKind::OpenEnded) ||
270 *kindField == std::to_underlying(VaultKind::ClosedEnded);
271}
272
273[[nodiscard]] bool
275{
276 auto const s = static_cast<std::int64_t>(sub);
277 auto const r = static_cast<std::int64_t>(red);
278 return r >= s + kMinInvestmentPeriod && r < s + kMaxInvestmentPeriod;
279}
280
281[[nodiscard]] VaultPhase
283{
284 XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::getVaultPhase : valid Vault sle");
285 return getVaultPhase(
286 view, (*vault)[~sfVaultKind], (*vault)[~sfSubscriptionDate], (*vault)[~sfRedemptionDate]);
287}
288
289[[nodiscard]] VaultPhase
291 ReadView const& view,
293 std::optional<std::uint32_t> subscriptionDate,
294 std::optional<std::uint32_t> redemptionDate)
295{
296 if (!vaultKind || *vaultKind != std::to_underlying(VaultKind::ClosedEnded))
297 return VaultPhase::NoPhase;
298
299 // Subscription includes now == SubscriptionDate; Investment starts
300 // strictly after SubscriptionDate.
301 if (!hasExpired(view, subscriptionDate, ExpiryComparison::Exclusive))
303 if (!hasExpired(view, redemptionDate))
306}
307
308[[nodiscard]] TER
310 ReadView const& view,
311 SLE::ConstRef issuance,
312 AccountID const& subject,
313 SuppressExpired suppressExpired)
314{
315 XRPL_ASSERT(
316 issuance && issuance->getType() == ltMPTOKEN_ISSUANCE,
317 "xrpl::checkVaultDomain : valid issuance SLE");
318
319 auto const maybeDomainID = issuance->at(~sfDomainID);
320 if (!maybeDomainID)
321 return tecNO_AUTH;
322
323 auto const err = credentials::validDomain(view, *maybeDomainID, subject);
324 if (err == tecEXPIRED && suppressExpired == SuppressExpired::Yes)
325 return tesSUCCESS;
326
327 return err;
328}
329
330} // namespace xrpl
bool integral() const
Definition Asset.h:133
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
Number truncate() const noexcept
A view into a ledger.
Definition ReadView.h:41
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
std::uint64_t mantissa() const noexcept
Definition STAmount.h:490
bool negative() const noexcept
Definition STAmount.h:484
Asset const & asset() const
Definition STAmount.h:496
int exponent() const noexcept
Definition STAmount.h:459
std::shared_ptr< STLedgerEntry const > const & ConstRef
constexpr Zero kZero
Definition Zero.h:30
TER validDomain(ReadView const &view, UInt256 domainID, AccountID const &subject)
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:573
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
VaultPhase
Lifecycle phase of a vault.
Definition Protocol.h:345
std::optional< STAmount > sharesToAssetsWithdraw(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const &shares, WaiveUnrealizedLoss waive=WaiveUnrealizedLoss::No)
From the perspective of a vault, return the number of assets to give the depositor when they redeem a...
bool isSoleShareholder(ReadView const &view, AccountID const &account, SLE::ConstRef issuance)
Returns true iff account holds all of the vault's outstanding shares — i.e.
bool debitIsNonZeroDust(Asset const &asset, Number const &total, Number const &amount)
Returns true if debiting amount from total (the current value of a vault's sfAssetsTotal or sfAssetsA...
TER checkVaultDomain(ReadView const &view, SLE::ConstRef issuance, AccountID const &subject, SuppressExpired suppressExpired)
Checks that subject belongs to the permissioned domain governing a vault's shares.
std::optional< STAmount > assetsToSharesWithdraw(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const &assets, TruncateShares truncate=TruncateShares::No, WaiveUnrealizedLoss waive=WaiveUnrealizedLoss::No)
From the perspective of a vault, return the number of shares to demand from the depositor when they a...
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp, ExpiryComparison comparison=ExpiryComparison::Inclusive)
Determines whether the given expiration time has passed.
Definition View.cpp:51
bool isValidClosedEndedGap(std::uint32_t sub, std::uint32_t red)
Returns true iff the (SubscriptionDate, RedemptionDate) gap of a closed-ended vault satisfies kMinInv...
std::optional< STAmount > sharesToAssetsDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const &shares)
From the perspective of a vault, return the number of assets to take from depositor when they receive...
int scale(Number const &number, Asset const &asset)
Get the scale of a Number for a given asset.
Definition STAmount.h:794
Number assetsTotalForWithdrawal(SLE::ConstRef vault, WaiveUnrealizedLoss waive)
Returns the assets backing outstanding shares for a withdrawal: sfAssetsTotal minus sfLossUnrealized,...
constexpr std::uint32_t kMaxInvestmentPeriod
Definition Protocol.h:371
std::expected< STAmount, TER > clampToAssetsTotalScale(SLE::ConstRef vault, STAmount const &delta)
Adjusts a requested asset change (delta) to match the decimal scale of the updated total vault assets...
VaultKind
Vault kind.
Definition Protocol.h:336
TruncateShares
Controls whether to truncate shares instead of rounding.
STAmount roundToScale(STAmount const &value, std::int32_t scale, Number::RoundingMode rounding=Number::getround())
Round an arbitrary precision Amount to the precision of an STAmount that has a given exponent.
VaultVersion
Vault ledger-entry schema versions.
Definition Protocol.h:327
std::optional< STAmount > assetsToSharesDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const &assets)
From the perspective of a vault, return the number of shares to give depositor when they offer a fixe...
VaultPhase getVaultPhase(ReadView const &view, SLE::ConstRef vault)
Returns the current lifecycle phase of a vault.
MPTID makeMptID(std::uint32_t const sequence, AccountID const &account)
Definition Indexes.cpp:206
constexpr Number abs(Number x) noexcept
Definition Number.h:876
VaultKind getVaultKind(SLE::ConstRef vault)
Resolves the VaultKind of a vault SLE.
void roundToAsset(A const &asset, Number &value)
Round an arbitrary precision Number IN PLACE to the precision of a given Asset.
Definition STAmount.h:735
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
TERSubset< CanCvtToTER > TER
Definition TER.h:654
@ tecNO_AUTH
Definition TER.h:308
@ tecEXPIRED
Definition TER.h:322
@ tecPRECISION_LOSS
Definition TER.h:371
SuppressExpired
Controls whether checkVaultDomain reports an expired credential as an error.
bool isValidVaultKind(STTx const &tx)
Returns true iff sfVaultKind is either absent from tx or is present and equal to a recognised VaultKi...
VaultVersion getVaultVersion(SLE::ConstRef vault)
Resolves a Vault's LEVersion, the single point every accounting touch point should call to determine ...
constexpr std::uint32_t kMinInvestmentPeriod
Bounds on the length of a closed-ended vault's Investment phase (RedemptionDate - SubscriptionDate).
Definition Protocol.h:369
@ tesSUCCESS
Definition TER.h:250
WaiveUnrealizedLoss
Controls whether the withdraw conversion helpers (assetsToSharesWithdraw and sharesToAssetsWithdraw) ...
T unexpected(T... args)