rippled
Loading...
Searching...
No Matches
VaultWithdraw.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/VaultWithdraw.h>
22#include <xrpld/ledger/View.h>
23
24#include <xrpl/protocol/AccountID.h>
25#include <xrpl/protocol/Feature.h>
26#include <xrpl/protocol/SField.h>
27#include <xrpl/protocol/STNumber.h>
28#include <xrpl/protocol/TER.h>
29#include <xrpl/protocol/TxFlags.h>
30
31namespace ripple {
32
35{
36 if (!ctx.rules.enabled(featureSingleAssetVault))
37 return temDISABLED;
38
39 if (auto const ter = preflight1(ctx))
40 return ter;
41
42 if (ctx.tx.getFlags() & tfUniversalMask)
43 return temINVALID_FLAG;
44
45 if (ctx.tx[sfVaultID] == beast::zero)
46 {
47 JLOG(ctx.j.debug()) << "VaultWithdraw: zero/empty vault ID.";
48 return temMALFORMED;
49 }
50
51 if (ctx.tx[sfAmount] <= beast::zero)
52 return temBAD_AMOUNT;
53
54 if (auto const destination = ctx.tx[~sfDestination];
55 destination && *destination == beast::zero)
56 {
57 JLOG(ctx.j.debug()) << "VaultWithdraw: zero/empty destination account.";
58 return temMALFORMED;
59 }
60
61 return preflight2(ctx);
62}
63
64TER
66{
67 auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
68 if (!vault)
69 return tecNO_ENTRY;
70
71 auto const assets = ctx.tx[sfAmount];
72 auto const vaultAsset = vault->at(sfAsset);
73 auto const vaultShare = vault->at(sfShareMPTID);
74 if (assets.asset() != vaultAsset && assets.asset() != vaultShare)
75 return tecWRONG_ASSET;
76
77 if (vaultAsset.native())
78 ; // No special checks for XRP
79 else if (vaultAsset.holds<MPTIssue>())
80 {
81 auto mptID = vaultAsset.get<MPTIssue>().getMptID();
82 auto issuance = ctx.view.read(keylet::mptIssuance(mptID));
83 if (!issuance)
85 if (!issuance->isFlag(lsfMPTCanTransfer))
86 {
87 // LCOV_EXCL_START
88 JLOG(ctx.j.error())
89 << "VaultWithdraw: vault assets are non-transferable.";
90 return tecNO_AUTH;
91 // LCOV_EXCL_STOP
92 }
93 }
94 else if (vaultAsset.holds<Issue>())
95 {
96 auto const issuer =
97 ctx.view.read(keylet::account(vaultAsset.getIssuer()));
98 if (!issuer)
99 {
100 // LCOV_EXCL_START
101 JLOG(ctx.j.error())
102 << "VaultWithdraw: missing issuer of vault assets.";
103 return tefINTERNAL;
104 // LCOV_EXCL_STOP
105 }
106 }
107
108 // Enforce valid withdrawal policy
109 if (vault->at(sfWithdrawalPolicy) != vaultStrategyFirstComeFirstServe)
110 {
111 // LCOV_EXCL_START
112 JLOG(ctx.j.error()) << "VaultWithdraw: invalid withdrawal policy.";
113 return tefINTERNAL;
114 // LCOV_EXCL_STOP
115 }
116
117 auto const account = ctx.tx[sfAccount];
118 auto const dstAcct = [&]() -> AccountID {
119 if (ctx.tx.isFieldPresent(sfDestination))
120 return ctx.tx.getAccountID(sfDestination);
121 return account;
122 }();
123
124 // Withdrawal to a 3rd party destination account is essentially a transfer,
125 // via shares in the vault. Enforce all the usual asset transfer checks.
126 if (account != dstAcct)
127 {
128 auto const sleDst = ctx.view.read(keylet::account(dstAcct));
129 if (sleDst == nullptr)
130 return tecNO_DST;
131
132 if (sleDst->getFlags() & lsfRequireDestTag)
133 return tecDST_TAG_NEEDED; // Cannot send without a tag
134
135 if (sleDst->getFlags() & lsfDepositAuth)
136 {
137 if (!ctx.view.exists(keylet::depositPreauth(dstAcct, account)))
138 return tecNO_PERMISSION;
139 }
140 }
141
142 // Destination MPToken must exist (if asset is an MPT)
143 if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct);
145 return ter;
146
147 // Cannot withdraw from a Vault an Asset frozen for the destination account
148 if (isFrozen(ctx.view, dstAcct, vaultAsset))
149 return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
150
151 if (isFrozen(ctx.view, account, vaultShare))
152 return tecLOCKED;
153
154 return tesSUCCESS;
155}
156
157TER
159{
160 auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
161 if (!vault)
162 return tefINTERNAL; // LCOV_EXCL_LINE
163
164 auto const mptIssuanceID = (*vault)[sfShareMPTID];
165 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
166 if (!sleIssuance)
167 {
168 // LCOV_EXCL_START
169 JLOG(j_.error()) << "VaultWithdraw: missing issuance of vault shares.";
170 return tefINTERNAL;
171 // LCOV_EXCL_STOP
172 }
173
174 // Note, we intentionally do not check lsfVaultPrivate flag on the Vault. If
175 // you have a share in the vault, it means you were at some point authorized
176 // to deposit into it, and this means you are also indefinitely authorized
177 // to withdraw from it.
178
179 auto amount = ctx_.tx[sfAmount];
180 auto const asset = vault->at(sfAsset);
181 auto const share = MPTIssue(mptIssuanceID);
182 STAmount shares, assets;
183 if (amount.asset() == asset)
184 {
185 // Fixed assets, variable shares.
186 assets = amount;
187 shares = assetsToSharesWithdraw(vault, sleIssuance, assets);
188 }
189 else if (amount.asset() == share)
190 {
191 // Fixed shares, variable assets.
192 shares = amount;
193 assets = sharesToAssetsWithdraw(vault, sleIssuance, shares);
194 }
195 else
196 return tefINTERNAL; // LCOV_EXCL_LINE
197
198 if (accountHolds(
199 view(),
200 account_,
201 share,
204 j_) < shares)
205 {
206 JLOG(j_.debug()) << "VaultWithdraw: account doesn't hold enough shares";
208 }
209
210 // The vault must have enough assets on hand. The vault may hold assets that
211 // it has already pledged. That is why we look at AssetAvailable instead of
212 // the pseudo-account balance.
213 if (*vault->at(sfAssetsAvailable) < assets)
214 {
215 JLOG(j_.debug()) << "VaultWithdraw: vault doesn't hold enough assets";
217 }
218
219 vault->at(sfAssetsTotal) -= assets;
220 vault->at(sfAssetsAvailable) -= assets;
221 view().update(vault);
222
223 auto const& vaultAccount = vault->at(sfAccount);
224 // Transfer shares from depositor to vault.
225 if (auto ter = accountSend(
226 view(), account_, vaultAccount, shares, j_, WaiveTransferFee::Yes))
227 return ter;
228
229 auto const dstAcct = [&]() -> AccountID {
230 if (ctx_.tx.isFieldPresent(sfDestination))
231 return ctx_.tx.getAccountID(sfDestination);
232 return account_;
233 }();
234
235 // Transfer assets from vault to depositor or destination account.
236 if (auto ter = accountSend(
237 view(), vaultAccount, dstAcct, assets, j_, WaiveTransferFee::Yes))
238 return ter;
239
240 // Sanity check
241 if (accountHolds(
242 view(),
243 vaultAccount,
244 assets.asset(),
247 j_) < beast::zero)
248 {
249 // LCOV_EXCL_START
250 JLOG(j_.error()) << "VaultWithdraw: negative balance of vault assets.";
251 return tefINTERNAL;
252 // LCOV_EXCL_STOP
253 }
254
255 return tesSUCCESS;
256}
257
258} // namespace ripple
Stream error() const
Definition: Journal.h:346
Stream debug() const
Definition: Journal.h:328
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
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
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
AccountID getAccountID(SField const &field) const
Definition: STObject.cpp:651
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
std::uint32_t getFlags() const
Definition: STObject.cpp:537
AccountID const account_
Definition: Transactor.h:143
ApplyView & view()
Definition: Transactor.h:159
beast::Journal const j_
Definition: Transactor.h:141
ApplyContext & ctx_
Definition: Transactor.h:140
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
TER doApply() override
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:35
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
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition: Indexes.cpp:335
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
STAmount sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition: View.cpp:2677
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account)
Check if the account lacks required authorization.
Definition: View.cpp:2268
@ lsfMPTCanTransfer
@ lsfRequireDestTag
@ lsfDepositAuth
@ ahIGNORE_AUTH
Definition: View.h:81
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:91
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:160
@ tecNO_ENTRY
Definition: TER.h:306
@ tecNO_DST
Definition: TER.h:290
@ tecOBJECT_NOT_FOUND
Definition: TER.h:326
@ tecFROZEN
Definition: TER.h:303
@ tecINSUFFICIENT_FUNDS
Definition: TER.h:325
@ tecNO_PERMISSION
Definition: TER.h:305
@ tecDST_TAG_NEEDED
Definition: TER.h:309
@ tecWRONG_ASSET
Definition: TER.h:360
@ 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
bool isTesSuccess(TER x) noexcept
Definition: TER.h:672
STAmount assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition: View.cpp:2658
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:63
std::uint8_t constexpr vaultStrategyFirstComeFirstServe
Vault withdrawal policies.
Definition: Protocol.h:123
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:79
ReadView const & view
Definition: Transactor.h:82
beast::Journal const j
Definition: Transactor.h:87
State information when preflighting a tx.
Definition: Transactor.h:34
beast::Journal const j
Definition: Transactor.h:41