rippled
Loading...
Searching...
No Matches
VaultDeposit.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2025 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
21#include <xrpld/app/tx/detail/VaultDeposit.h>
22
23#include <xrpl/ledger/CredentialHelpers.h>
24#include <xrpl/ledger/View.h>
25#include <xrpl/protocol/Feature.h>
26#include <xrpl/protocol/Indexes.h>
27#include <xrpl/protocol/LedgerFormats.h>
28#include <xrpl/protocol/MPTIssue.h>
29#include <xrpl/protocol/SField.h>
30#include <xrpl/protocol/STNumber.h>
31#include <xrpl/protocol/TER.h>
32#include <xrpl/protocol/TxFlags.h>
33
34namespace ripple {
35
38{
39 if (!ctx.rules.enabled(featureSingleAssetVault))
40 return temDISABLED;
41
42 if (auto const ter = preflight1(ctx))
43 return ter;
44
45 if (ctx.tx.getFlags() & tfUniversalMask)
46 return temINVALID_FLAG;
47
48 if (ctx.tx[sfVaultID] == beast::zero)
49 {
50 JLOG(ctx.j.debug()) << "VaultDeposit: zero/empty vault ID.";
51 return temMALFORMED;
52 }
53
54 if (ctx.tx[sfAmount] <= beast::zero)
55 return temBAD_AMOUNT;
56
57 return preflight2(ctx);
58}
59
60TER
62{
63 auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
64 if (!vault)
65 return tecNO_ENTRY;
66
67 auto const account = ctx.tx[sfAccount];
68 auto const assets = ctx.tx[sfAmount];
69 auto const vaultAsset = vault->at(sfAsset);
70 if (assets.asset() != vaultAsset)
71 return tecWRONG_ASSET;
72
73 if (vaultAsset.native())
74 ; // No special checks for XRP
75 else if (vaultAsset.holds<MPTIssue>())
76 {
77 auto mptID = vaultAsset.get<MPTIssue>().getMptID();
78 auto issuance = ctx.view.read(keylet::mptIssuance(mptID));
79 if (!issuance)
81 if (!issuance->isFlag(lsfMPTCanTransfer))
82 {
83 // LCOV_EXCL_START
84 JLOG(ctx.j.error())
85 << "VaultDeposit: vault assets are non-transferable.";
86 return tecNO_AUTH;
87 // LCOV_EXCL_STOP
88 }
89 }
90 else if (vaultAsset.holds<Issue>())
91 {
92 auto const issuer =
93 ctx.view.read(keylet::account(vaultAsset.getIssuer()));
94 if (!issuer)
95 {
96 // LCOV_EXCL_START
97 JLOG(ctx.j.error())
98 << "VaultDeposit: missing issuer of vault assets.";
99 return tefINTERNAL;
100 // LCOV_EXCL_STOP
101 }
102 }
103
104 auto const mptIssuanceID = vault->at(sfShareMPTID);
105 auto const vaultShare = MPTIssue(mptIssuanceID);
106 if (vaultShare == assets.asset())
107 {
108 // LCOV_EXCL_START
109 JLOG(ctx.j.error())
110 << "VaultDeposit: vault shares and assets cannot be same.";
111 return tefINTERNAL;
112 // LCOV_EXCL_STOP
113 }
114
115 auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
116 if (!sleIssuance)
117 {
118 // LCOV_EXCL_START
119 JLOG(ctx.j.error())
120 << "VaultDeposit: missing issuance of vault shares.";
121 return tefINTERNAL;
122 // LCOV_EXCL_STOP
123 }
124
125 if (sleIssuance->isFlag(lsfMPTLocked))
126 {
127 // LCOV_EXCL_START
128 JLOG(ctx.j.error())
129 << "VaultDeposit: issuance of vault shares is locked.";
130 return tefINTERNAL;
131 // LCOV_EXCL_STOP
132 }
133
134 // Cannot deposit inside Vault an Asset frozen for the depositor
135 if (isFrozen(ctx.view, account, vaultAsset))
136 return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
137
138 // Cannot deposit if the shares of the vault are frozen
139 if (isFrozen(ctx.view, account, vaultShare))
140 return tecLOCKED;
141
142 if (vault->isFlag(lsfVaultPrivate) && account != vault->at(sfOwner))
143 {
144 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
145 // Since this is a private vault and the account is not its owner, we
146 // perform authorization check based on DomainID read from sleIssuance.
147 // Had the vault shares been a regular MPToken, we would allow
148 // authorization granted by the Issuer explicitly, but Vault uses Issuer
149 // pseudo-account, which cannot grant an authorization.
150 if (maybeDomainID)
151 {
152 // As per validDomain documentation, we suppress tecEXPIRED error
153 // here, so we can delete any expired credentials inside doApply.
154 if (auto const err =
155 credentials::validDomain(ctx.view, *maybeDomainID, account);
156 !isTesSuccess(err) && err != tecEXPIRED)
157 return err;
158 }
159 else
160 return tecNO_AUTH;
161 }
162
163 // Source MPToken must exist (if asset is an MPT)
164 if (auto const ter = requireAuth(ctx.view, vaultAsset, account);
165 !isTesSuccess(ter))
166 return ter;
167
168 if (accountHolds(
169 ctx.view,
170 account,
171 vaultAsset,
174 ctx.j) < assets)
176
177 return tesSUCCESS;
178}
179
180TER
182{
183 auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
184 if (!vault)
185 return tefINTERNAL; // LCOV_EXCL_LINE
186
187 auto const amount = ctx_.tx[sfAmount];
188 // Make sure the depositor can hold shares.
189 auto const mptIssuanceID = (*vault)[sfShareMPTID];
190 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
191 if (!sleIssuance)
192 {
193 // LCOV_EXCL_START
194 JLOG(j_.error()) << "VaultDeposit: missing issuance of vault shares.";
195 return tefINTERNAL;
196 // LCOV_EXCL_STOP
197 }
198
199 auto const& vaultAccount = vault->at(sfAccount);
200 // Note, vault owner is always authorized
201 if (vault->isFlag(lsfVaultPrivate) && account_ != vault->at(sfOwner))
202 {
203 if (auto const err = enforceMPTokenAuthorization(
204 ctx_.view(), mptIssuanceID, account_, mPriorBalance, j_);
205 !isTesSuccess(err))
206 return err;
207 }
208 else // !vault->isFlag(lsfVaultPrivate) || account_ == vault->at(sfOwner)
209 {
210 // No authorization needed, but must ensure there is MPToken
211 auto sleMpt = view().read(keylet::mptoken(mptIssuanceID, account_));
212 if (!sleMpt)
213 {
214 if (auto const err = authorizeMPToken(
215 view(),
217 mptIssuanceID->value(),
218 account_,
219 ctx_.journal);
220 !isTesSuccess(err))
221 return err;
222 }
223
224 // If the vault is private, set the authorized flag for the vault owner
225 if (vault->isFlag(lsfVaultPrivate))
226 {
227 // This follows from the reverse of the outer enclosing if condition
228 XRPL_ASSERT(
229 account_ == vault->at(sfOwner),
230 "ripple::VaultDeposit::doApply : account is owner");
231 if (auto const err = authorizeMPToken(
232 view(),
233 mPriorBalance, // priorBalance
234 mptIssuanceID->value(), // mptIssuanceID
235 sleIssuance->at(sfIssuer), // account
237 {}, // flags
238 account_ // holderID
239 );
240 !isTesSuccess(err))
241 return err;
242 }
243 }
244
245 STAmount sharesCreated = {vault->at(sfShareMPTID)}, assetsDeposited;
246 try
247 {
248 // Compute exchange before transferring any amounts.
249 {
250 auto const maybeShares =
251 assetsToSharesDeposit(vault, sleIssuance, amount);
252 if (!maybeShares)
253 return tecINTERNAL; // LCOV_EXCL_LINE
254 sharesCreated = *maybeShares;
255 }
256 if (sharesCreated == beast::zero)
257 return tecPRECISION_LOSS;
258
259 auto const maybeAssets =
260 sharesToAssetsDeposit(vault, sleIssuance, sharesCreated);
261 if (!maybeAssets)
262 return tecINTERNAL; // LCOV_EXCL_LINE
263 else if (*maybeAssets > amount)
264 {
265 // LCOV_EXCL_START
266 JLOG(j_.error()) << "VaultDeposit: would take more than offered.";
267 return tecINTERNAL;
268 // LCOV_EXCL_STOP
269 }
270 assetsDeposited = *maybeAssets;
271 }
272 catch (std::overflow_error const&)
273 {
274 // It's easy to hit this exception from Number with large enough Scale
275 // so we avoid spamming the log and only use debug here.
276 JLOG(j_.debug()) //
277 << "VaultDeposit: overflow error with"
278 << " scale=" << (int)vault->at(sfScale).value() //
279 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
280 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
281 << ", amount=" << amount;
282 return tecPATH_DRY;
283 }
284
285 XRPL_ASSERT(
286 sharesCreated.asset() != assetsDeposited.asset(),
287 "ripple::VaultDeposit::doApply : assets are not shares");
288
289 vault->at(sfAssetsTotal) += assetsDeposited;
290 vault->at(sfAssetsAvailable) += assetsDeposited;
291 view().update(vault);
292
293 // A deposit must not push the vault over its limit.
294 auto const maximum = *vault->at(sfAssetsMaximum);
295 if (maximum != 0 && *vault->at(sfAssetsTotal) > maximum)
296 return tecLIMIT_EXCEEDED;
297
298 // Transfer assets from depositor to vault.
299 if (auto const ter = accountSend(
300 view(),
301 account_,
302 vaultAccount,
303 assetsDeposited,
304 j_,
306 !isTesSuccess(ter))
307 return ter;
308
309 // Sanity check
310 if (accountHolds(
311 view(),
312 account_,
313 assetsDeposited.asset(),
316 j_) < beast::zero)
317 {
318 // LCOV_EXCL_START
319 JLOG(j_.error()) << "VaultDeposit: negative balance of account assets.";
320 return tefINTERNAL;
321 // LCOV_EXCL_STOP
322 }
323
324 // Transfer shares from vault to depositor.
325 if (auto const ter = accountSend(
326 view(),
327 vaultAccount,
328 account_,
329 sharesCreated,
330 j_,
332 !isTesSuccess(ter))
333 return ter;
334
335 return tesSUCCESS;
336}
337
338} // namespace ripple
Stream error() const
Definition Journal.h:346
Stream debug() const
Definition Journal.h:328
ApplyView & view()
beast::Journal const journal
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
A currency issued by an account.
Definition Issue.h:33
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:130
Asset const & asset() const
Definition STAmount.h:483
std::uint32_t getFlags() const
Definition STObject.cpp:537
AccountID const account_
Definition Transactor.h:145
ApplyView & view()
Definition Transactor.h:161
beast::Journal const j_
Definition Transactor.h:143
XRPAmount mPriorBalance
Definition Transactor.h:146
ApplyContext & ctx_
Definition Transactor.h:141
TER doApply() override
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
constexpr value_type value() const
Returns the underlying value.
Definition XRPAmount.h:239
TER validDomain(ReadView const &view, uint256 domainID, AccountID const &subject)
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:540
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:526
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:564
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
@ fhZERO_IF_FROZEN
Definition View.h:77
@ fhIGNORE_FREEZE
Definition View.h:77
std::optional< STAmount > sharesToAssetsDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:2885
@ lsfMPTCanTransfer
@ lsfVaultPrivate
std::optional< STAmount > assetsToSharesDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition View.cpp:2857
@ ahZERO_IF_UNAUTHORIZED
Definition View.h:80
@ ahIGNORE_AUTH
Definition View.h:80
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2174
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:247
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:2464
@ tefINTERNAL
Definition TER.h:173
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
@ tecNO_ENTRY
Definition TER.h:306
@ tecLIMIT_EXCEEDED
Definition TER.h:361
@ tecOBJECT_NOT_FOUND
Definition TER.h:326
@ tecFROZEN
Definition TER.h:303
@ tecINSUFFICIENT_FUNDS
Definition TER.h:325
@ tecINTERNAL
Definition TER.h:310
@ tecPRECISION_LOSS
Definition TER.h:363
@ tecWRONG_ASSET
Definition TER.h:360
@ tecPATH_DRY
Definition TER.h:294
@ tecEXPIRED
Definition TER.h:314
@ tecNO_AUTH
Definition TER.h:300
@ tecLOCKED
Definition TER.h:358
@ tesSUCCESS
Definition TER.h:244
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:384
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
constexpr std::uint32_t tfUniversalMask
Definition TxFlags.h:63
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
Definition View.cpp:2582
TER authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
Definition View.cpp:1281
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
@ temBAD_AMOUNT
Definition TER.h:89
@ temMALFORMED
Definition TER.h:87
@ temINVALID_FLAG
Definition TER.h:111
@ temDISABLED
Definition TER.h:114
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:80
ReadView const & view
Definition Transactor.h:83
beast::Journal const j
Definition Transactor.h:88
State information when preflighting a tx.
Definition Transactor.h:35
beast::Journal const j
Definition Transactor.h:42