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/misc/CredentialHelpers.h>
21#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
22#include <xrpld/app/tx/detail/VaultDeposit.h>
23#include <xrpld/ledger/View.h>
24
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/STNumber.h>
30#include <xrpl/protocol/TER.h>
31#include <xrpl/protocol/TxFlags.h>
32
33namespace ripple {
34
37{
38 if (!ctx.rules.enabled(featureSingleAssetVault))
39 return temDISABLED;
40
41 if (auto const ter = preflight1(ctx))
42 return ter;
43
44 if (ctx.tx.getFlags() & tfUniversalMask)
45 return temINVALID_FLAG;
46
47 if (ctx.tx[sfVaultID] == beast::zero)
48 {
49 JLOG(ctx.j.debug()) << "VaultDeposit: zero/empty vault ID.";
50 return temMALFORMED;
51 }
52
53 if (ctx.tx[sfAmount] <= beast::zero)
54 return temBAD_AMOUNT;
55
56 return preflight2(ctx);
57}
58
59TER
61{
62 auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
63 if (!vault)
64 return tecNO_ENTRY;
65
66 auto const account = ctx.tx[sfAccount];
67 auto const assets = ctx.tx[sfAmount];
68 auto const vaultAsset = vault->at(sfAsset);
69 if (assets.asset() != vaultAsset)
70 return tecWRONG_ASSET;
71
72 if (vaultAsset.native())
73 ; // No special checks for XRP
74 else if (vaultAsset.holds<MPTIssue>())
75 {
76 auto mptID = vaultAsset.get<MPTIssue>().getMptID();
77 auto issuance = ctx.view.read(keylet::mptIssuance(mptID));
78 if (!issuance)
80 if (!issuance->isFlag(lsfMPTCanTransfer))
81 {
82 // LCOV_EXCL_START
83 JLOG(ctx.j.error())
84 << "VaultDeposit: vault assets are non-transferable.";
85 return tecNO_AUTH;
86 // LCOV_EXCL_STOP
87 }
88 }
89 else if (vaultAsset.holds<Issue>())
90 {
91 auto const issuer =
92 ctx.view.read(keylet::account(vaultAsset.getIssuer()));
93 if (!issuer)
94 {
95 // LCOV_EXCL_START
96 JLOG(ctx.j.error())
97 << "VaultDeposit: missing issuer of vault assets.";
98 return tefINTERNAL;
99 // LCOV_EXCL_STOP
100 }
101 }
102
103 auto const mptIssuanceID = vault->at(sfShareMPTID);
104 auto const vaultShare = MPTIssue(mptIssuanceID);
105 if (vaultShare == assets.asset())
106 {
107 // LCOV_EXCL_START
108 JLOG(ctx.j.error())
109 << "VaultDeposit: vault shares and assets cannot be same.";
110 return tefINTERNAL;
111 // LCOV_EXCL_STOP
112 }
113
114 auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
115 if (!sleIssuance)
116 {
117 // LCOV_EXCL_START
118 JLOG(ctx.j.error())
119 << "VaultDeposit: missing issuance of vault shares.";
120 return tefINTERNAL;
121 // LCOV_EXCL_STOP
122 }
123
124 if (sleIssuance->isFlag(lsfMPTLocked))
125 {
126 // LCOV_EXCL_START
127 JLOG(ctx.j.error())
128 << "VaultDeposit: issuance of vault shares is locked.";
129 return tefINTERNAL;
130 // LCOV_EXCL_STOP
131 }
132
133 // Cannot deposit inside Vault an Asset frozen for the depositor
134 if (isFrozen(ctx.view, account, vaultAsset))
135 return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
136
137 // Cannot deposit if the shares of the vault are frozen
138 if (isFrozen(ctx.view, account, vaultShare))
139 return tecLOCKED;
140
141 if (vault->isFlag(tfVaultPrivate) && account != vault->at(sfOwner))
142 {
143 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
144 // Since this is a private vault and the account is not its owner, we
145 // perform authorization check based on DomainID read from sleIssuance.
146 // Had the vault shares been a regular MPToken, we would allow
147 // authorization granted by the Issuer explicitly, but Vault uses Issuer
148 // pseudo-account, which cannot grant an authorization.
149 if (maybeDomainID)
150 {
151 // As per validDomain documentation, we suppress tecEXPIRED error
152 // here, so we can delete any expired credentials inside doApply.
153 if (auto const err =
154 credentials::validDomain(ctx.view, *maybeDomainID, account);
155 !isTesSuccess(err) && err != tecEXPIRED)
156 return err;
157 }
158 else
159 return tecNO_AUTH;
160 }
161
162 // Source MPToken must exist (if asset is an MPT)
163 if (auto const ter = requireAuth(ctx.view, vaultAsset, account);
165 return ter;
166
167 if (accountHolds(
168 ctx.view,
169 account,
170 vaultAsset,
173 ctx.j) < assets)
175
176 return tesSUCCESS;
177}
178
179TER
181{
182 auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
183 if (!vault)
184 return tefINTERNAL; // LCOV_EXCL_LINE
185
186 auto const assets = ctx_.tx[sfAmount];
187 // Make sure the depositor can hold shares.
188 auto const mptIssuanceID = (*vault)[sfShareMPTID];
189 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
190 if (!sleIssuance)
191 {
192 // LCOV_EXCL_START
193 JLOG(j_.error()) << "VaultDeposit: missing issuance of vault shares.";
194 return tefINTERNAL;
195 // LCOV_EXCL_STOP
196 }
197
198 auto const& vaultAccount = vault->at(sfAccount);
199 // Note, vault owner is always authorized
200 if ((vault->getFlags() & tfVaultPrivate) && account_ != vault->at(sfOwner))
201 {
202 if (auto const err = enforceMPTokenAuthorization(
203 ctx_.view(), mptIssuanceID, account_, mPriorBalance, j_);
204 !isTesSuccess(err))
205 return err;
206 }
207 else
208 {
209 // No authorization needed, but must ensure there is MPToken
210 auto sleMpt = view().read(keylet::mptoken(mptIssuanceID, account_));
211 if (!sleMpt)
212 {
213 if (auto const err = MPTokenAuthorize::authorize(
214 view(),
216 {.priorBalance = mPriorBalance,
217 .mptIssuanceID = mptIssuanceID->value(),
218 .account = account_});
219 !isTesSuccess(err))
220 return err;
221 }
222
223 // If the vault is private, set the authorized flag for the vault owner
224 if (vault->isFlag(tfVaultPrivate))
225 {
226 if (auto const err = MPTokenAuthorize::authorize(
227 view(),
229 {
230 .priorBalance = mPriorBalance,
231 .mptIssuanceID = mptIssuanceID->value(),
232 .account = sleIssuance->at(sfIssuer),
233 .holderID = account_,
234 });
235 !isTesSuccess(err))
236 return err;
237 }
238 }
239
240 // Compute exchange before transferring any amounts.
241 auto const shares = assetsToSharesDeposit(vault, sleIssuance, assets);
242 XRPL_ASSERT(
243 shares.asset() != assets.asset(),
244 "ripple::VaultDeposit::doApply : assets are not shares");
245
246 vault->at(sfAssetsTotal) += assets;
247 vault->at(sfAssetsAvailable) += assets;
248 view().update(vault);
249
250 // A deposit must not push the vault over its limit.
251 auto const maximum = *vault->at(sfAssetsMaximum);
252 if (maximum != 0 && *vault->at(sfAssetsTotal) > maximum)
253 return tecLIMIT_EXCEEDED;
254
255 // Transfer assets from depositor to vault.
256 if (auto ter = accountSend(
257 view(), account_, vaultAccount, assets, j_, WaiveTransferFee::Yes))
258 return ter;
259
260 // Sanity check
261 if (accountHolds(
262 view(),
263 account_,
264 assets.asset(),
267 j_) < beast::zero)
268 {
269 // LCOV_EXCL_START
270 JLOG(j_.error()) << "VaultDeposit: negative balance of account assets.";
271 return tefINTERNAL;
272 // LCOV_EXCL_STOP
273 }
274
275 // Transfer shares from vault to depositor.
276 if (auto ter = accountSend(
277 view(), vaultAccount, account_, shares, j_, WaiveTransferFee::Yes))
278 return ter;
279
280 return tesSUCCESS;
281}
282
283} // namespace ripple
Stream error() const
Definition: Journal.h:346
Stream debug() const
Definition: Journal.h:328
ApplyView & view()
Definition: ApplyContext.h:55
beast::Journal const journal
Definition: ApplyContext.h:52
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:36
static TER authorize(ApplyView &view, beast::Journal journal, MPTAuthorizeArgs const &args)
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
std::uint32_t getFlags() const
Definition: STObject.cpp:537
AccountID const account_
Definition: Transactor.h:93
ApplyView & view()
Definition: Transactor.h:109
beast::Journal const j_
Definition: Transactor.h:91
XRPAmount mPriorBalance
Definition: Transactor.h:94
ApplyContext & ctx_
Definition: Transactor.h:90
TER doApply() override
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:35
TER validDomain(ReadView const &view, uint256 domainID, AccountID const &subject)
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition: Indexes.cpp:533
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition: Indexes.cpp:519
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition: Indexes.cpp:557
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:177
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
@ fhZERO_IF_FROZEN
Definition: View.h:78
@ fhIGNORE_FREEZE
Definition: View.h:78
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account)
Check if the account lacks required authorization.
Definition: View.cpp:2268
@ lsfMPTCanTransfer
@ lsfMPTLocked
constexpr std::uint32_t const tfVaultPrivate
Definition: TxFlags.h:238
@ ahZERO_IF_UNAUTHORIZED
Definition: View.h:81
@ ahIGNORE_AUTH
Definition: View.h:81
bool isTesSuccess(TER x)
Definition: TER.h:672
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:83
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:2373
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition: View.cpp:250
@ tefINTERNAL
Definition: TER.h:173
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:144
@ 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
@ tecWRONG_ASSET
Definition: TER.h:360
@ 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:387
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:62
STAmount assetsToSharesDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition: View.cpp:2640
TER accountSend(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Calls static accountSendIOU if saAmount represents Issue.
Definition: View.cpp:1978
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:603
@ 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:55
ReadView const & view
Definition: Transactor.h:58
beast::Journal const j
Definition: Transactor.h:62
State information when preflighting a tx.
Definition: Transactor.h:34
beast::Journal const j
Definition: Transactor.h:40