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.tx[sfVaultID] == beast::zero)
40 {
41 JLOG(ctx.j.debug()) << "VaultDeposit: zero/empty vault ID.";
42 return temMALFORMED;
43 }
44
45 if (ctx.tx[sfAmount] <= beast::zero)
46 return temBAD_AMOUNT;
47
48 return tesSUCCESS;
49}
50
51TER
53{
54 auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
55 if (!vault)
56 return tecNO_ENTRY;
57
58 auto const account = ctx.tx[sfAccount];
59 auto const assets = ctx.tx[sfAmount];
60 auto const vaultAsset = vault->at(sfAsset);
61 if (assets.asset() != vaultAsset)
62 return tecWRONG_ASSET;
63
64 if (vaultAsset.native())
65 ; // No special checks for XRP
66 else if (vaultAsset.holds<MPTIssue>())
67 {
68 auto mptID = vaultAsset.get<MPTIssue>().getMptID();
69 auto issuance = ctx.view.read(keylet::mptIssuance(mptID));
70 if (!issuance)
72 if (!issuance->isFlag(lsfMPTCanTransfer))
73 {
74 // LCOV_EXCL_START
75 JLOG(ctx.j.error())
76 << "VaultDeposit: vault assets are non-transferable.";
77 return tecNO_AUTH;
78 // LCOV_EXCL_STOP
79 }
80 }
81 else if (vaultAsset.holds<Issue>())
82 {
83 auto const issuer =
84 ctx.view.read(keylet::account(vaultAsset.getIssuer()));
85 if (!issuer)
86 {
87 // LCOV_EXCL_START
88 JLOG(ctx.j.error())
89 << "VaultDeposit: missing issuer of vault assets.";
90 return tefINTERNAL;
91 // LCOV_EXCL_STOP
92 }
93 }
94
95 auto const mptIssuanceID = vault->at(sfShareMPTID);
96 auto const vaultShare = MPTIssue(mptIssuanceID);
97 if (vaultShare == assets.asset())
98 {
99 // LCOV_EXCL_START
100 JLOG(ctx.j.error())
101 << "VaultDeposit: vault shares and assets cannot be same.";
102 return tefINTERNAL;
103 // LCOV_EXCL_STOP
104 }
105
106 auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
107 if (!sleIssuance)
108 {
109 // LCOV_EXCL_START
110 JLOG(ctx.j.error())
111 << "VaultDeposit: missing issuance of vault shares.";
112 return tefINTERNAL;
113 // LCOV_EXCL_STOP
114 }
115
116 if (sleIssuance->isFlag(lsfMPTLocked))
117 {
118 // LCOV_EXCL_START
119 JLOG(ctx.j.error())
120 << "VaultDeposit: issuance of vault shares is locked.";
121 return tefINTERNAL;
122 // LCOV_EXCL_STOP
123 }
124
125 // Cannot deposit inside Vault an Asset frozen for the depositor
126 if (isFrozen(ctx.view, account, vaultAsset))
127 return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
128
129 // Cannot deposit if the shares of the vault are frozen
130 if (isFrozen(ctx.view, account, vaultShare))
131 return tecLOCKED;
132
133 if (vault->isFlag(lsfVaultPrivate) && account != vault->at(sfOwner))
134 {
135 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
136 // Since this is a private vault and the account is not its owner, we
137 // perform authorization check based on DomainID read from sleIssuance.
138 // Had the vault shares been a regular MPToken, we would allow
139 // authorization granted by the Issuer explicitly, but Vault uses Issuer
140 // pseudo-account, which cannot grant an authorization.
141 if (maybeDomainID)
142 {
143 // As per validDomain documentation, we suppress tecEXPIRED error
144 // here, so we can delete any expired credentials inside doApply.
145 if (auto const err =
146 credentials::validDomain(ctx.view, *maybeDomainID, account);
147 !isTesSuccess(err) && err != tecEXPIRED)
148 return err;
149 }
150 else
151 return tecNO_AUTH;
152 }
153
154 // Source MPToken must exist (if asset is an MPT)
155 if (auto const ter = requireAuth(ctx.view, vaultAsset, account);
156 !isTesSuccess(ter))
157 return ter;
158
159 if (accountHolds(
160 ctx.view,
161 account,
162 vaultAsset,
165 ctx.j) < assets)
167
168 return tesSUCCESS;
169}
170
171TER
173{
174 auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
175 if (!vault)
176 return tefINTERNAL; // LCOV_EXCL_LINE
177
178 auto const amount = ctx_.tx[sfAmount];
179 // Make sure the depositor can hold shares.
180 auto const mptIssuanceID = (*vault)[sfShareMPTID];
181 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
182 if (!sleIssuance)
183 {
184 // LCOV_EXCL_START
185 JLOG(j_.error()) << "VaultDeposit: missing issuance of vault shares.";
186 return tefINTERNAL;
187 // LCOV_EXCL_STOP
188 }
189
190 auto const& vaultAccount = vault->at(sfAccount);
191 // Note, vault owner is always authorized
192 if (vault->isFlag(lsfVaultPrivate) && account_ != vault->at(sfOwner))
193 {
194 if (auto const err = enforceMPTokenAuthorization(
195 ctx_.view(), mptIssuanceID, account_, mPriorBalance, j_);
196 !isTesSuccess(err))
197 return err;
198 }
199 else // !vault->isFlag(lsfVaultPrivate) || account_ == vault->at(sfOwner)
200 {
201 // No authorization needed, but must ensure there is MPToken
202 auto sleMpt = view().read(keylet::mptoken(mptIssuanceID, account_));
203 if (!sleMpt)
204 {
205 if (auto const err = authorizeMPToken(
206 view(),
208 mptIssuanceID->value(),
209 account_,
210 ctx_.journal);
211 !isTesSuccess(err))
212 return err;
213 }
214
215 // If the vault is private, set the authorized flag for the vault owner
216 if (vault->isFlag(lsfVaultPrivate))
217 {
218 // This follows from the reverse of the outer enclosing if condition
219 XRPL_ASSERT(
220 account_ == vault->at(sfOwner),
221 "ripple::VaultDeposit::doApply : account is owner");
222 if (auto const err = authorizeMPToken(
223 view(),
224 mPriorBalance, // priorBalance
225 mptIssuanceID->value(), // mptIssuanceID
226 sleIssuance->at(sfIssuer), // account
228 {}, // flags
229 account_ // holderID
230 );
231 !isTesSuccess(err))
232 return err;
233 }
234 }
235
236 STAmount sharesCreated = {vault->at(sfShareMPTID)}, assetsDeposited;
237 try
238 {
239 // Compute exchange before transferring any amounts.
240 {
241 auto const maybeShares =
242 assetsToSharesDeposit(vault, sleIssuance, amount);
243 if (!maybeShares)
244 return tecINTERNAL; // LCOV_EXCL_LINE
245 sharesCreated = *maybeShares;
246 }
247 if (sharesCreated == beast::zero)
248 return tecPRECISION_LOSS;
249
250 auto const maybeAssets =
251 sharesToAssetsDeposit(vault, sleIssuance, sharesCreated);
252 if (!maybeAssets)
253 return tecINTERNAL; // LCOV_EXCL_LINE
254 else if (*maybeAssets > amount)
255 {
256 // LCOV_EXCL_START
257 JLOG(j_.error()) << "VaultDeposit: would take more than offered.";
258 return tecINTERNAL;
259 // LCOV_EXCL_STOP
260 }
261 assetsDeposited = *maybeAssets;
262 }
263 catch (std::overflow_error const&)
264 {
265 // It's easy to hit this exception from Number with large enough Scale
266 // so we avoid spamming the log and only use debug here.
267 JLOG(j_.debug()) //
268 << "VaultDeposit: overflow error with"
269 << " scale=" << (int)vault->at(sfScale).value() //
270 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
271 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
272 << ", amount=" << amount;
273 return tecPATH_DRY;
274 }
275
276 XRPL_ASSERT(
277 sharesCreated.asset() != assetsDeposited.asset(),
278 "ripple::VaultDeposit::doApply : assets are not shares");
279
280 vault->at(sfAssetsTotal) += assetsDeposited;
281 vault->at(sfAssetsAvailable) += assetsDeposited;
282 view().update(vault);
283
284 // A deposit must not push the vault over its limit.
285 auto const maximum = *vault->at(sfAssetsMaximum);
286 if (maximum != 0 && *vault->at(sfAssetsTotal) > maximum)
287 return tecLIMIT_EXCEEDED;
288
289 // Transfer assets from depositor to vault.
290 if (auto const ter = accountSend(
291 view(),
292 account_,
293 vaultAccount,
294 assetsDeposited,
295 j_,
297 !isTesSuccess(ter))
298 return ter;
299
300 // Sanity check
301 if (accountHolds(
302 view(),
303 account_,
304 assetsDeposited.asset(),
307 j_) < beast::zero)
308 {
309 // LCOV_EXCL_START
310 JLOG(j_.error()) << "VaultDeposit: negative balance of account assets.";
311 return tefINTERNAL;
312 // LCOV_EXCL_STOP
313 }
314
315 // Transfer shares from vault to depositor.
316 if (auto const ter = accountSend(
317 view(),
318 vaultAccount,
319 account_,
320 sharesCreated,
321 j_,
323 !isTesSuccess(ter))
324 return ter;
325
326 return tesSUCCESS;
327}
328
329} // 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.
Asset const & asset() const
Definition STAmount.h:483
AccountID const account_
Definition Transactor.h:147
ApplyView & view()
Definition Transactor.h:163
beast::Journal const j_
Definition Transactor.h:145
XRPAmount mPriorBalance
Definition Transactor.h:148
ApplyContext & ctx_
Definition Transactor.h:143
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
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
@ 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
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
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