rippled
Loading...
Searching...
No Matches
VaultWithdraw.cpp
1#include <xrpld/app/tx/detail/VaultWithdraw.h>
2
3#include <xrpl/ledger/CredentialHelpers.h>
4#include <xrpl/ledger/View.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/TER.h>
10#include <xrpl/protocol/TxFlags.h>
11
12namespace ripple {
13
16{
17 if (ctx.tx[sfVaultID] == beast::zero)
18 {
19 JLOG(ctx.j.debug()) << "VaultWithdraw: zero/empty vault ID.";
20 return temMALFORMED;
21 }
22
23 if (ctx.tx[sfAmount] <= beast::zero)
24 return temBAD_AMOUNT;
25
26 if (auto const destination = ctx.tx[~sfDestination];
27 destination.has_value())
28 {
29 if (*destination == beast::zero)
30 {
31 JLOG(ctx.j.debug())
32 << "VaultWithdraw: zero/empty destination account.";
33 return temMALFORMED;
34 }
35 }
36
37 return tesSUCCESS;
38}
39
40TER
42{
43 auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
44 if (!vault)
45 return tecNO_ENTRY;
46
47 auto const assets = ctx.tx[sfAmount];
48 auto const vaultAsset = vault->at(sfAsset);
49 auto const vaultShare = vault->at(sfShareMPTID);
50 if (assets.asset() != vaultAsset && assets.asset() != vaultShare)
51 return tecWRONG_ASSET;
52
53 if (vaultAsset.native())
54 ; // No special checks for XRP
55 else if (vaultAsset.holds<MPTIssue>())
56 {
57 auto mptID = vaultAsset.get<MPTIssue>().getMptID();
58 auto issuance = ctx.view.read(keylet::mptIssuance(mptID));
59 if (!issuance)
61 if (!issuance->isFlag(lsfMPTCanTransfer))
62 {
63 // LCOV_EXCL_START
64 JLOG(ctx.j.error())
65 << "VaultWithdraw: vault assets are non-transferable.";
66 return tecNO_AUTH;
67 // LCOV_EXCL_STOP
68 }
69 }
70 else if (vaultAsset.holds<Issue>())
71 {
72 auto const issuer =
73 ctx.view.read(keylet::account(vaultAsset.getIssuer()));
74 if (!issuer)
75 {
76 // LCOV_EXCL_START
77 JLOG(ctx.j.error())
78 << "VaultWithdraw: missing issuer of vault assets.";
79 return tefINTERNAL;
80 // LCOV_EXCL_STOP
81 }
82 }
83
84 // Enforce valid withdrawal policy
85 if (vault->at(sfWithdrawalPolicy) != vaultStrategyFirstComeFirstServe)
86 {
87 // LCOV_EXCL_START
88 JLOG(ctx.j.error()) << "VaultWithdraw: invalid withdrawal policy.";
89 return tefINTERNAL;
90 // LCOV_EXCL_STOP
91 }
92
93 auto const account = ctx.tx[sfAccount];
94 auto const dstAcct = ctx.tx[~sfDestination].value_or(account);
95 auto const sleDst = ctx.view.read(keylet::account(dstAcct));
96 if (sleDst == nullptr)
97 return account == dstAcct ? tecINTERNAL : tecNO_DST;
98
99 if (sleDst->isFlag(lsfRequireDestTag) &&
100 !ctx.tx.isFieldPresent(sfDestinationTag))
101 return tecDST_TAG_NEEDED; // Cannot send without a tag
102
103 // Withdrawal to a 3rd party destination account is essentially a transfer,
104 // via shares in the vault. Enforce all the usual asset transfer checks.
105 if (account != dstAcct && sleDst->isFlag(lsfDepositAuth))
106 {
107 if (!ctx.view.exists(keylet::depositPreauth(dstAcct, account)))
108 return tecNO_PERMISSION;
109 }
110
111 // If sending to Account (i.e. not a transfer), we will also create (only
112 // if authorized) a trust line or MPToken as needed, in doApply().
113 // Destination MPToken or trust line must exist if _not_ sending to Account.
114 AuthType const authType =
115 account == dstAcct ? AuthType::WeakAuth : AuthType::StrongAuth;
116 if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType);
117 !isTesSuccess(ter))
118 return ter;
119
120 // Cannot withdraw from a Vault an Asset frozen for the destination account
121 if (auto const ret = checkFrozen(ctx.view, dstAcct, vaultAsset))
122 return ret;
123
124 // Cannot return shares to the vault, if the underlying asset was frozen for
125 // the submitter
126 if (auto const ret = checkFrozen(ctx.view, account, vaultShare))
127 return ret;
128
129 return tesSUCCESS;
130}
131
132TER
134{
135 auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
136 if (!vault)
137 return tefINTERNAL; // LCOV_EXCL_LINE
138
139 auto const mptIssuanceID = *((*vault)[sfShareMPTID]);
140 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
141 if (!sleIssuance)
142 {
143 // LCOV_EXCL_START
144 JLOG(j_.error()) << "VaultWithdraw: missing issuance of vault shares.";
145 return tefINTERNAL;
146 // LCOV_EXCL_STOP
147 }
148
149 // Note, we intentionally do not check lsfVaultPrivate flag on the Vault. If
150 // you have a share in the vault, it means you were at some point authorized
151 // to deposit into it, and this means you are also indefinitely authorized
152 // to withdraw from it.
153
154 auto const amount = ctx_.tx[sfAmount];
155 Asset const vaultAsset = vault->at(sfAsset);
156 MPTIssue const share{mptIssuanceID};
157 STAmount sharesRedeemed = {share};
158 STAmount assetsWithdrawn;
159 try
160 {
161 if (amount.asset() == vaultAsset)
162 {
163 // Fixed assets, variable shares.
164 {
165 auto const maybeShares =
166 assetsToSharesWithdraw(vault, sleIssuance, amount);
167 if (!maybeShares)
168 return tecINTERNAL; // LCOV_EXCL_LINE
169 sharesRedeemed = *maybeShares;
170 }
171
172 if (sharesRedeemed == beast::zero)
173 return tecPRECISION_LOSS;
174 auto const maybeAssets =
175 sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed);
176 if (!maybeAssets)
177 return tecINTERNAL; // LCOV_EXCL_LINE
178 assetsWithdrawn = *maybeAssets;
179 }
180 else if (amount.asset() == share)
181 {
182 // Fixed shares, variable assets.
183 sharesRedeemed = amount;
184 auto const maybeAssets =
185 sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed);
186 if (!maybeAssets)
187 return tecINTERNAL; // LCOV_EXCL_LINE
188 assetsWithdrawn = *maybeAssets;
189 }
190 else
191 return tefINTERNAL; // LCOV_EXCL_LINE
192 }
193 catch (std::overflow_error const&)
194 {
195 // It's easy to hit this exception from Number with large enough Scale
196 // so we avoid spamming the log and only use debug here.
197 JLOG(j_.debug()) //
198 << "VaultWithdraw: overflow error with"
199 << " scale=" << (int)vault->at(sfScale).value() //
200 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
201 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
202 << ", amount=" << amount.value();
203 return tecPATH_DRY;
204 }
205
206 if (accountHolds(
207 view(),
208 account_,
209 share,
212 j_) < sharesRedeemed)
213 {
214 JLOG(j_.debug()) << "VaultWithdraw: account doesn't hold enough shares";
216 }
217
218 auto assetsAvailable = vault->at(sfAssetsAvailable);
219 auto assetsTotal = vault->at(sfAssetsTotal);
220 [[maybe_unused]] auto const lossUnrealized = vault->at(sfLossUnrealized);
221 XRPL_ASSERT(
222 lossUnrealized <= (assetsTotal - assetsAvailable),
223 "ripple::VaultWithdraw::doApply : loss and assets do balance");
224
225 // The vault must have enough assets on hand. The vault may hold assets
226 // that it has already pledged. That is why we look at AssetAvailable
227 // instead of the pseudo-account balance.
228 if (*assetsAvailable < assetsWithdrawn)
229 {
230 JLOG(j_.debug()) << "VaultWithdraw: vault doesn't hold enough assets";
232 }
233
234 assetsTotal -= assetsWithdrawn;
235 assetsAvailable -= assetsWithdrawn;
236 view().update(vault);
237
238 auto const& vaultAccount = vault->at(sfAccount);
239 // Transfer shares from depositor to vault.
240 if (auto const ter = accountSend(
241 view(),
242 account_,
243 vaultAccount,
244 sharesRedeemed,
245 j_,
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(
256 view(), account_, sharesRedeemed.asset(), j_);
257 isTesSuccess(ter))
258 {
259 JLOG(j_.debug()) //
260 << "VaultWithdraw: removed empty MPToken for vault shares"
261 << " MPTID=" << to_string(mptIssuanceID) //
262 << " account=" << toBase58(account_);
263 }
264 else if (ter != tecHAS_OBLIGATIONS)
265 {
266 // LCOV_EXCL_START
267 JLOG(j_.error()) //
268 << "VaultWithdraw: failed to remove MPToken for vault shares"
269 << " MPTID=" << to_string(mptIssuanceID) //
270 << " account=" << toBase58(account_) //
271 << " with result: " << transToken(ter);
272 return ter;
273 // LCOV_EXCL_STOP
274 }
275 // else quietly ignore, account balance is not zero
276 }
277
278 auto const dstAcct = ctx_.tx[~sfDestination].value_or(account_);
279 if (!vaultAsset.native() && //
280 dstAcct != vaultAsset.getIssuer() && //
281 dstAcct == account_)
282 {
283 if (auto const ter = addEmptyHolding(
284 view(), account_, mPriorBalance, vaultAsset, j_);
285 !isTesSuccess(ter) && ter != tecDUPLICATE)
286 return ter;
287 }
288
289 // Transfer assets from vault to depositor or destination account.
290 if (auto const ter = accountSend(
291 view(),
292 vaultAccount,
293 dstAcct,
294 assetsWithdrawn,
295 j_,
297 !isTesSuccess(ter))
298 return ter;
299
300 // Sanity check
301 if (accountHolds(
302 view(),
303 vaultAccount,
304 assetsWithdrawn.asset(),
307 j_) < beast::zero)
308 {
309 // LCOV_EXCL_START
310 JLOG(j_.error()) << "VaultWithdraw: negative balance of vault assets.";
311 return tefINTERNAL;
312 // LCOV_EXCL_STOP
313 }
314
315 return tesSUCCESS;
316}
317
318} // namespace ripple
Stream error() const
Definition Journal.h:327
Stream debug() const
Definition Journal.h:309
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.
bool native() const
Definition Asset.h:82
AccountID const & getIssuer() const
Definition Asset.cpp:17
A currency issued by an account.
Definition Issue.h:14
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.
Asset const & asset() const
Definition STAmount.h:464
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:465
AccountID const account_
Definition Transactor.h:128
ApplyView & view()
Definition Transactor.h:144
beast::Journal const j_
Definition Transactor.h:126
XRPAmount mPriorBalance
Definition Transactor.h:129
ApplyContext & ctx_
Definition Transactor.h:124
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:507
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:545
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:323
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:159
@ fhZERO_IF_FROZEN
Definition View.h:58
@ fhIGNORE_FREEZE
Definition View.h:58
@ lsfMPTCanTransfer
@ lsfRequireDestTag
AuthType
Definition View.h:767
@ ahIGNORE_AUTH
Definition View.h:61
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)
Definition View.cpp:2922
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:2172
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:2466
@ tefINTERNAL
Definition TER.h:154
std::optional< STAmount > sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:2951
std::string transToken(TER code)
Definition TER.cpp:245
@ tecNO_ENTRY
Definition TER.h:288
@ tecNO_DST
Definition TER.h:272
@ tecOBJECT_NOT_FOUND
Definition TER.h:308
@ tecDUPLICATE
Definition TER.h:297
@ tecINSUFFICIENT_FUNDS
Definition TER.h:307
@ tecINTERNAL
Definition TER.h:292
@ tecNO_PERMISSION
Definition TER.h:287
@ tecDST_TAG_NEEDED
Definition TER.h:291
@ tecPRECISION_LOSS
Definition TER.h:345
@ tecHAS_OBLIGATIONS
Definition TER.h:299
@ tecWRONG_ASSET
Definition TER.h:342
@ tecPATH_DRY
Definition TER.h:276
@ tecNO_AUTH
Definition TER.h:282
@ tesSUCCESS
Definition TER.h:226
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1197
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:368
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
std::uint8_t constexpr vaultStrategyFirstComeFirstServe
Vault withdrawal policies.
Definition Protocol.h:106
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1498
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:590
@ temBAD_AMOUNT
Definition TER.h:70
@ temMALFORMED
Definition TER.h:68
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
beast::Journal const j
Definition Transactor.h:69
State information when preflighting a tx.
Definition Transactor.h:16
beast::Journal const j
Definition Transactor.h:23