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.has_value())
56 {
57 if (*destination == beast::zero)
58 {
59 JLOG(ctx.j.debug())
60 << "VaultWithdraw: zero/empty destination account.";
61 return temMALFORMED;
62 }
63 }
64 else if (ctx.tx.isFieldPresent(sfDestinationTag))
65 {
66 JLOG(ctx.j.debug()) << "VaultWithdraw: sfDestinationTag is set but "
67 "sfDestination is not";
68 return temMALFORMED;
69 }
70
71 return preflight2(ctx);
72}
73
74TER
76{
77 auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
78 if (!vault)
79 return tecNO_ENTRY;
80
81 auto const assets = ctx.tx[sfAmount];
82 auto const vaultAsset = vault->at(sfAsset);
83 auto const vaultShare = vault->at(sfShareMPTID);
84 if (assets.asset() != vaultAsset && assets.asset() != vaultShare)
85 return tecWRONG_ASSET;
86
87 if (vaultAsset.native())
88 ; // No special checks for XRP
89 else if (vaultAsset.holds<MPTIssue>())
90 {
91 auto mptID = vaultAsset.get<MPTIssue>().getMptID();
92 auto issuance = ctx.view.read(keylet::mptIssuance(mptID));
93 if (!issuance)
95 if (!issuance->isFlag(lsfMPTCanTransfer))
96 {
97 // LCOV_EXCL_START
98 JLOG(ctx.j.error())
99 << "VaultWithdraw: vault assets are non-transferable.";
100 return tecNO_AUTH;
101 // LCOV_EXCL_STOP
102 }
103 }
104 else if (vaultAsset.holds<Issue>())
105 {
106 auto const issuer =
107 ctx.view.read(keylet::account(vaultAsset.getIssuer()));
108 if (!issuer)
109 {
110 // LCOV_EXCL_START
111 JLOG(ctx.j.error())
112 << "VaultWithdraw: missing issuer of vault assets.";
113 return tefINTERNAL;
114 // LCOV_EXCL_STOP
115 }
116 }
117
118 // Enforce valid withdrawal policy
119 if (vault->at(sfWithdrawalPolicy) != vaultStrategyFirstComeFirstServe)
120 {
121 // LCOV_EXCL_START
122 JLOG(ctx.j.error()) << "VaultWithdraw: invalid withdrawal policy.";
123 return tefINTERNAL;
124 // LCOV_EXCL_STOP
125 }
126
127 auto const account = ctx.tx[sfAccount];
128 auto const dstAcct = [&]() -> AccountID {
129 if (ctx.tx.isFieldPresent(sfDestination))
130 return ctx.tx.getAccountID(sfDestination);
131 return account;
132 }();
133
134 // Withdrawal to a 3rd party destination account is essentially a transfer,
135 // via shares in the vault. Enforce all the usual asset transfer checks.
136 AuthType authType = AuthType::Legacy;
137 if (account != dstAcct)
138 {
139 auto const sleDst = ctx.view.read(keylet::account(dstAcct));
140 if (sleDst == nullptr)
141 return tecNO_DST;
142
143 if (sleDst->isFlag(lsfRequireDestTag) &&
144 !ctx.tx.isFieldPresent(sfDestinationTag))
145 return tecDST_TAG_NEEDED; // Cannot send without a tag
146
147 if (sleDst->isFlag(lsfDepositAuth))
148 {
149 if (!ctx.view.exists(keylet::depositPreauth(dstAcct, account)))
150 return tecNO_PERMISSION;
151 }
152 // The destination account must have consented to receive the asset by
153 // creating a RippleState or MPToken
154 authType = AuthType::StrongAuth;
155 }
156
157 // Destination MPToken (for an MPT) or trust line (for an IOU) must exist
158 // if not sending to Account.
159 if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType);
160 !isTesSuccess(ter))
161 return ter;
162
163 // Cannot withdraw from a Vault an Asset frozen for the destination account
164 if (auto const ret = checkFrozen(ctx.view, dstAcct, vaultAsset))
165 return ret;
166
167 if (auto const ret = checkFrozen(ctx.view, account, vaultShare))
168 return ret;
169
170 return tesSUCCESS;
171}
172
173TER
175{
176 auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
177 if (!vault)
178 return tefINTERNAL; // LCOV_EXCL_LINE
179
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()) << "VaultWithdraw: missing issuance of vault shares.";
186 return tefINTERNAL;
187 // LCOV_EXCL_STOP
188 }
189
190 // Note, we intentionally do not check lsfVaultPrivate flag on the Vault. If
191 // you have a share in the vault, it means you were at some point authorized
192 // to deposit into it, and this means you are also indefinitely authorized
193 // to withdraw from it.
194
195 auto amount = ctx_.tx[sfAmount];
196 auto const asset = vault->at(sfAsset);
197 auto const share = MPTIssue(mptIssuanceID);
198 STAmount shares, assets;
199 if (amount.asset() == asset)
200 {
201 // Fixed assets, variable shares.
202 assets = amount;
203 shares = assetsToSharesWithdraw(vault, sleIssuance, assets);
204 }
205 else if (amount.asset() == share)
206 {
207 // Fixed shares, variable assets.
208 shares = amount;
209 assets = sharesToAssetsWithdraw(vault, sleIssuance, shares);
210 }
211 else
212 return tefINTERNAL; // LCOV_EXCL_LINE
213
214 if (accountHolds(
215 view(),
216 account_,
217 share,
220 j_) < shares)
221 {
222 JLOG(j_.debug()) << "VaultWithdraw: account doesn't hold enough shares";
224 }
225
226 // The vault must have enough assets on hand. The vault may hold assets that
227 // it has already pledged. That is why we look at AssetAvailable instead of
228 // the pseudo-account balance.
229 if (*vault->at(sfAssetsAvailable) < assets)
230 {
231 JLOG(j_.debug()) << "VaultWithdraw: vault doesn't hold enough assets";
233 }
234
235 vault->at(sfAssetsTotal) -= assets;
236 vault->at(sfAssetsAvailable) -= assets;
237 view().update(vault);
238
239 auto const& vaultAccount = vault->at(sfAccount);
240 // Transfer shares from depositor to vault.
241 if (auto ter = accountSend(
242 view(), account_, vaultAccount, shares, j_, WaiveTransferFee::Yes))
243 return ter;
244
245 auto const dstAcct = [&]() -> AccountID {
246 if (ctx_.tx.isFieldPresent(sfDestination))
247 return ctx_.tx.getAccountID(sfDestination);
248 return account_;
249 }();
250
251 // Transfer assets from vault to depositor or destination account.
252 if (auto ter = accountSend(
253 view(), vaultAccount, dstAcct, assets, j_, WaiveTransferFee::Yes))
254 return ter;
255
256 // Sanity check
257 if (accountHolds(
258 view(),
259 vaultAccount,
260 assets.asset(),
263 j_) < beast::zero)
264 {
265 // LCOV_EXCL_START
266 JLOG(j_.error()) << "VaultWithdraw: negative balance of vault assets.";
267 return tefINTERNAL;
268 // LCOV_EXCL_STOP
269 }
270
271 return tesSUCCESS;
272}
273
274} // 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:33
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
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
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition: Indexes.cpp:342
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:25
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition: View.h:179
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType)
Check if the account lacks required authorization.
Definition: View.cpp:2301
@ 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:2734
@ lsfMPTCanTransfer
@ lsfRequireDestTag
@ lsfDepositAuth
AuthType
Definition: View.h:760
@ 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
@ 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
@ 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
@ 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:386
bool isTesSuccess(TER x) noexcept
Definition: TER.h:674
STAmount assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition: View.cpp:2715
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:2011
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: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