rippled
Loading...
Searching...
No Matches
VaultWithdraw.cpp
1#include <xrpl/ledger/View.h>
2#include <xrpl/ledger/helpers/CredentialHelpers.h>
3#include <xrpl/ledger/helpers/TokenHelpers.h>
4#include <xrpl/ledger/helpers/VaultHelpers.h>
5#include <xrpl/protocol/AccountID.h>
6#include <xrpl/protocol/Feature.h>
7#include <xrpl/protocol/SField.h>
8#include <xrpl/protocol/STNumber.h>
9#include <xrpl/protocol/STTakesAsset.h>
10#include <xrpl/protocol/TER.h>
11#include <xrpl/protocol/TxFlags.h>
12#include <xrpl/tx/transactors/vault/VaultWithdraw.h>
13
14namespace xrpl {
15
18{
19 if (ctx.tx[sfVaultID] == beast::zero)
20 {
21 JLOG(ctx.j.debug()) << "VaultWithdraw: zero/empty vault ID.";
22 return temMALFORMED;
23 }
24
25 if (ctx.tx[sfAmount] <= beast::zero)
26 return temBAD_AMOUNT;
27
28 if (auto const destination = ctx.tx[~sfDestination])
29 {
30 if (*destination == beast::zero)
31 {
32 return temMALFORMED;
33 }
34 }
35
36 return tesSUCCESS;
37}
38
39TER
41{
42 auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
43 if (!vault)
44 return tecNO_ENTRY;
45
46 auto const amount = ctx.tx[sfAmount];
47 auto const vaultAsset = vault->at(sfAsset);
48 auto const vaultShare = vault->at(sfShareMPTID);
49 if (amount.asset() != vaultAsset && amount.asset() != vaultShare)
50 return tecWRONG_ASSET;
51
52 auto const& vaultAccount = vault->at(sfAccount);
53 auto const& account = ctx.tx[sfAccount];
54 auto const& dstAcct = ctx.tx[~sfDestination].value_or(account);
55 if (auto ter = canTransfer(ctx.view, vaultAsset, vaultAccount, dstAcct); !isTesSuccess(ter))
56 {
57 JLOG(ctx.j.debug()) << "VaultWithdraw: vault assets are non-transferable.";
58 return ter;
59 }
60
61 // Enforce valid withdrawal policy
62 if (vault->at(sfWithdrawalPolicy) != vaultStrategyFirstComeFirstServe)
63 {
64 // LCOV_EXCL_START
65 JLOG(ctx.j.error()) << "VaultWithdraw: invalid withdrawal policy.";
66 return tefINTERNAL;
67 // LCOV_EXCL_STOP
68 }
69
70 if (ctx.view.rules().enabled(fixSecurity3_1_3) && amount.asset() == vaultShare)
71 {
72 // Post-fixSecurity3_1_3: if the user specified shares, convert
73 // to the equivalent asset amount before checking withdrawal
74 // limits. Pre-amendment the limit check was skipped for
75 // share-denominated withdrawals.
76 auto const sleIssuance = ctx.view.read(keylet::mptIssuance(vaultShare));
77 if (!sleIssuance)
78 {
79 // LCOV_EXCL_START
80 JLOG(ctx.j.error()) << "VaultWithdraw: missing issuance of vault shares.";
81 return tefINTERNAL;
82 // LCOV_EXCL_STOP
83 }
84
85 try
86 {
87 auto const maybeAssets = sharesToAssetsWithdraw(vault, sleIssuance, amount);
88 if (!maybeAssets)
89 return tefINTERNAL; // LCOV_EXCL_LINE
90
91 if (auto const ret = canWithdraw(
92 ctx.view,
93 account,
94 dstAcct,
95 *maybeAssets,
96 ctx.tx.isFieldPresent(sfDestinationTag)))
97 return ret;
98 }
99 catch (std::overflow_error const&)
100 {
101 // It's easy to hit this exception from Number with large enough Scale
102 // so we avoid spamming the log and only use debug here.
103 JLOG(ctx.j.debug()) //
104 << "VaultWithdraw: overflow error with"
105 << " scale=" << (int)vault->at(sfScale) //
106 << ", assetsTotal=" << vault->at(sfAssetsTotal)
107 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
108 << ", amount=" << amount.value();
109 return tecPATH_DRY;
110 }
111 }
112 else
113 {
114 if (auto const ret = canWithdraw(ctx.view, ctx.tx))
115 return ret;
116 }
117
118 // If sending to Account (i.e. not a transfer), we will also create (only
119 // if authorized) a trust line or MPToken as needed, in doApply().
120 // Destination MPToken or trust line must exist if _not_ sending to Account.
121 AuthType const authType = account == dstAcct ? AuthType::WeakAuth : AuthType::StrongAuth;
122 if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType); !isTesSuccess(ter))
123 return ter;
124
125 // Cannot withdraw from a Vault an Asset frozen for the destination account
126 if (auto const ret = checkFrozen(ctx.view, dstAcct, vaultAsset))
127 return ret;
128
129 // Cannot return shares to the vault, if the underlying asset was frozen for
130 // the submitter
131 if (auto const ret = checkFrozen(ctx.view, account, vaultShare))
132 return ret;
133
134 return tesSUCCESS;
135}
136
137TER
139{
140 auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
141 if (!vault)
142 return tefINTERNAL; // LCOV_EXCL_LINE
143
144 auto const mptIssuanceID = *((*vault)[sfShareMPTID]);
145 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
146 if (!sleIssuance)
147 {
148 // LCOV_EXCL_START
149 JLOG(j_.error()) << "VaultWithdraw: missing issuance of vault shares.";
150 return tefINTERNAL;
151 // LCOV_EXCL_STOP
152 }
153
154 // Note, we intentionally do not check lsfVaultPrivate flag on the Vault. If
155 // you have a share in the vault, it means you were at some point authorized
156 // to deposit into it, and this means you are also indefinitely authorized
157 // to withdraw from it.
158
159 auto const amount = ctx_.tx[sfAmount];
160 Asset const vaultAsset = vault->at(sfAsset);
161
162 MPTIssue const share{mptIssuanceID};
163 STAmount sharesRedeemed = {share};
164 STAmount assetsWithdrawn;
165 try
166 {
167 if (amount.asset() == vaultAsset)
168 {
169 // Fixed assets, variable shares.
170 {
171 auto const maybeShares = assetsToSharesWithdraw(vault, sleIssuance, amount);
172 if (!maybeShares)
173 return tecINTERNAL; // LCOV_EXCL_LINE
174 sharesRedeemed = *maybeShares;
175 }
176
177 if (sharesRedeemed == beast::zero)
178 return tecPRECISION_LOSS;
179 auto const maybeAssets = sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed);
180 if (!maybeAssets)
181 return tecINTERNAL; // LCOV_EXCL_LINE
182 assetsWithdrawn = *maybeAssets;
183 }
184 else if (amount.asset() == share)
185 {
186 // Fixed shares, variable assets.
187 sharesRedeemed = amount;
188 auto const maybeAssets = sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed);
189 if (!maybeAssets)
190 return tecINTERNAL; // LCOV_EXCL_LINE
191 assetsWithdrawn = *maybeAssets;
192 }
193 else
194 {
195 return tefINTERNAL; // LCOV_EXCL_LINE
196 }
197 }
198 catch (std::overflow_error const&)
199 {
200 // It's easy to hit this exception from Number with large enough Scale
201 // so we avoid spamming the log and only use debug here.
202 JLOG(j_.debug()) //
203 << "VaultWithdraw: overflow error with"
204 << " scale=" << (int)vault->at(sfScale).value() //
205 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
206 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
207 << ", amount=" << amount.value();
208 return tecPATH_DRY;
209 }
210
211 if (accountHolds(
212 view(),
213 account_,
214 share,
217 j_) < sharesRedeemed)
218 {
219 JLOG(j_.debug()) << "VaultWithdraw: account doesn't hold enough shares";
221 }
222
223 auto assetsAvailable = vault->at(sfAssetsAvailable);
224 auto assetsTotal = vault->at(sfAssetsTotal);
225 [[maybe_unused]] auto const lossUnrealized = vault->at(sfLossUnrealized);
226 XRPL_ASSERT(
227 lossUnrealized <= (assetsTotal - assetsAvailable),
228 "xrpl::VaultWithdraw::doApply : loss and assets do balance");
229
230 // The vault must have enough assets on hand. The vault may hold assets
231 // that it has already pledged. That is why we look at AssetAvailable
232 // instead of the pseudo-account balance.
233 if (*assetsAvailable < assetsWithdrawn)
234 {
235 JLOG(j_.debug()) << "VaultWithdraw: vault doesn't hold enough assets";
237 }
238
239 assetsTotal -= assetsWithdrawn;
240 assetsAvailable -= assetsWithdrawn;
241 view().update(vault);
242
243 auto const& vaultAccount = vault->at(sfAccount);
244 // Transfer shares from depositor to vault.
245 if (auto const ter =
246 accountSend(view(), account_, vaultAccount, sharesRedeemed, j_, WaiveTransferFee::Yes);
247 !isTesSuccess(ter))
248 return ter;
249
250 // Try to remove MPToken for shares, if the account balance is zero. Vault
251 // pseudo-account will never set lsfMPTAuthorized, so we ignore flags.
252 // Keep MPToken if holder is the vault owner.
253 if (account_ != vault->at(sfOwner))
254 {
255 if (auto const ter = removeEmptyHolding(view(), account_, sharesRedeemed.asset(), j_);
256 isTesSuccess(ter))
257 {
258 JLOG(j_.debug()) //
259 << "VaultWithdraw: removed empty MPToken for vault shares"
260 << " MPTID=" << to_string(mptIssuanceID) //
261 << " account=" << toBase58(account_);
262 }
263 else if (ter != tecHAS_OBLIGATIONS)
264 {
265 // LCOV_EXCL_START
266 JLOG(j_.error()) //
267 << "VaultWithdraw: failed to remove MPToken for vault shares"
268 << " MPTID=" << to_string(mptIssuanceID) //
269 << " account=" << toBase58(account_) //
270 << " with result: " << transToken(ter);
271 return ter;
272 // LCOV_EXCL_STOP
273 }
274 // else quietly ignore, account balance is not zero
275 }
276
277 auto const dstAcct = ctx_.tx[~sfDestination].value_or(account_);
278
279 associateAsset(*vault, vaultAsset);
280
281 return doWithdraw(
282 view(), ctx_.tx, account_, dstAcct, vaultAccount, preFeeBalance_, assetsWithdrawn, j_);
283}
284
285} // namespace xrpl
Stream error() const
Definition Journal.h:319
Stream debug() const
Definition Journal.h:301
STTx const & tx
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.
virtual Rules const & rules() const =0
Returns the tx processing rules.
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:120
Asset const & asset() const
Definition STAmount.h:457
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:456
AccountID const account_
Definition Transactor.h:116
beast::Journal const j_
Definition Transactor.h:114
ApplyView & view()
Definition Transactor.h:132
XRPAmount preFeeBalance_
Definition Transactor.h:117
ApplyContext & ctx_
Definition Transactor.h:112
TER doApply() override
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:474
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:504
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ fhZERO_IF_FROZEN
std::uint8_t constexpr vaultStrategyFirstComeFirstServe
Vault withdrawal policies.
Definition Protocol.h:241
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
TER doWithdraw(ApplyView &view, STTx const &tx, AccountID const &senderAcct, AccountID const &dstAcct, AccountID const &sourceAcct, XRPAmount priorBalance, STAmount const &amount, beast::Journal j)
Definition View.cpp:402
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
@ tefINTERNAL
Definition TER.h:153
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:92
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
std::string transToken(TER code)
Definition TER.cpp:243
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.
std::optional< STAmount > assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets, TruncateShares truncate=TruncateShares::no)
From the perspective of a vault, return the number of shares to demand from the depositor when they a...
@ ahIGNORE_AUTH
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
@ temMALFORMED
Definition TER.h:67
@ temBAD_AMOUNT
Definition TER.h:69
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
std::optional< STAmount > sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
From the perspective of a vault, return the number of assets to give the depositor when they redeem a...
@ tecWRONG_ASSET
Definition TER.h:341
@ tecNO_ENTRY
Definition TER.h:287
@ tecPATH_DRY
Definition TER.h:275
@ tecINTERNAL
Definition TER.h:291
@ tecINSUFFICIENT_FUNDS
Definition TER.h:306
@ tecPRECISION_LOSS
Definition TER.h:344
@ tecHAS_OBLIGATIONS
Definition TER.h:298
TER canWithdraw(ReadView const &view, AccountID const &from, AccountID const &to, SLE::const_ref toSle, STAmount const &amount, bool hasDestinationTag)
Checks that can withdraw funds from an object to itself or a destination.
Definition View.cpp:356
void associateAsset(STLedgerEntry &sle, Asset const &asset)
Associate an Asset with all sMD_NeedsAsset fields in a ledger entry.
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, MPTIssue const &mptIssue, beast::Journal journal)
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:582
@ tesSUCCESS
Definition TER.h:225
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, int depth=0)
Check if the account lacks required authorization for MPT.
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:57
ReadView const & view
Definition Transactor.h:60
beast::Journal const j
Definition Transactor.h:65
State information when preflighting a tx.
Definition Transactor.h:14
beast::Journal const j
Definition Transactor.h:21