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 if (auto const ret = checkFrozen(ctx.view, account, vaultShare))
125 return ret;
126
127 return tesSUCCESS;
128}
129
130TER
132{
133 auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
134 if (!vault)
135 return tefINTERNAL; // LCOV_EXCL_LINE
136
137 auto const mptIssuanceID = *((*vault)[sfShareMPTID]);
138 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
139 if (!sleIssuance)
140 {
141 // LCOV_EXCL_START
142 JLOG(j_.error()) << "VaultWithdraw: missing issuance of vault shares.";
143 return tefINTERNAL;
144 // LCOV_EXCL_STOP
145 }
146
147 // Note, we intentionally do not check lsfVaultPrivate flag on the Vault. If
148 // you have a share in the vault, it means you were at some point authorized
149 // to deposit into it, and this means you are also indefinitely authorized
150 // to withdraw from it.
151
152 auto const amount = ctx_.tx[sfAmount];
153 Asset const vaultAsset = vault->at(sfAsset);
154 MPTIssue const share{mptIssuanceID};
155 STAmount sharesRedeemed = {share};
156 STAmount assetsWithdrawn;
157 try
158 {
159 if (amount.asset() == vaultAsset)
160 {
161 // Fixed assets, variable shares.
162 {
163 auto const maybeShares =
164 assetsToSharesWithdraw(vault, sleIssuance, amount);
165 if (!maybeShares)
166 return tecINTERNAL; // LCOV_EXCL_LINE
167 sharesRedeemed = *maybeShares;
168 }
169
170 if (sharesRedeemed == beast::zero)
171 return tecPRECISION_LOSS;
172 auto const maybeAssets =
173 sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed);
174 if (!maybeAssets)
175 return tecINTERNAL; // LCOV_EXCL_LINE
176 assetsWithdrawn = *maybeAssets;
177 }
178 else if (amount.asset() == share)
179 {
180 // Fixed shares, variable assets.
181 sharesRedeemed = amount;
182 auto const maybeAssets =
183 sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed);
184 if (!maybeAssets)
185 return tecINTERNAL; // LCOV_EXCL_LINE
186 assetsWithdrawn = *maybeAssets;
187 }
188 else
189 return tefINTERNAL; // LCOV_EXCL_LINE
190 }
191 catch (std::overflow_error const&)
192 {
193 // It's easy to hit this exception from Number with large enough Scale
194 // so we avoid spamming the log and only use debug here.
195 JLOG(j_.debug()) //
196 << "VaultWithdraw: overflow error with"
197 << " scale=" << (int)vault->at(sfScale).value() //
198 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
199 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
200 << ", amount=" << amount.value();
201 return tecPATH_DRY;
202 }
203
204 if (accountHolds(
205 view(),
206 account_,
207 share,
210 j_) < sharesRedeemed)
211 {
212 JLOG(j_.debug()) << "VaultWithdraw: account doesn't hold enough shares";
214 }
215
216 auto assetsAvailable = vault->at(sfAssetsAvailable);
217 auto assetsTotal = vault->at(sfAssetsTotal);
218 [[maybe_unused]] auto const lossUnrealized = vault->at(sfLossUnrealized);
219 XRPL_ASSERT(
220 lossUnrealized <= (assetsTotal - assetsAvailable),
221 "ripple::VaultWithdraw::doApply : loss and assets do balance");
222
223 // The vault must have enough assets on hand. The vault may hold assets
224 // that it has already pledged. That is why we look at AssetAvailable
225 // instead of the pseudo-account balance.
226 if (*assetsAvailable < assetsWithdrawn)
227 {
228 JLOG(j_.debug()) << "VaultWithdraw: vault doesn't hold enough assets";
230 }
231
232 assetsTotal -= assetsWithdrawn;
233 assetsAvailable -= assetsWithdrawn;
234 view().update(vault);
235
236 auto const& vaultAccount = vault->at(sfAccount);
237 // Transfer shares from depositor to vault.
238 if (auto const ter = accountSend(
239 view(),
240 account_,
241 vaultAccount,
242 sharesRedeemed,
243 j_,
245 !isTesSuccess(ter))
246 return ter;
247
248 // Try to remove MPToken for shares, if the account balance is zero. Vault
249 // pseudo-account will never set lsfMPTAuthorized, so we ignore flags.
250 // Keep MPToken if holder is the vault owner.
251 if (account_ != vault->at(sfOwner))
252 {
253 if (auto const ter = removeEmptyHolding(
254 view(), account_, sharesRedeemed.asset(), j_);
255 isTesSuccess(ter))
256 {
257 JLOG(j_.debug()) //
258 << "VaultWithdraw: removed empty MPToken for vault shares"
259 << " MPTID=" << to_string(mptIssuanceID) //
260 << " account=" << toBase58(account_);
261 }
262 else if (ter != tecHAS_OBLIGATIONS)
263 {
264 // LCOV_EXCL_START
265 JLOG(j_.error()) //
266 << "VaultWithdraw: failed to remove MPToken for vault shares"
267 << " MPTID=" << to_string(mptIssuanceID) //
268 << " account=" << toBase58(account_) //
269 << " with result: " << transToken(ter);
270 return ter;
271 // LCOV_EXCL_STOP
272 }
273 // else quietly ignore, account balance is not zero
274 }
275
276 auto const dstAcct = ctx_.tx[~sfDestination].value_or(account_);
277 if (!vaultAsset.native() && //
278 dstAcct != vaultAsset.getIssuer() && //
279 dstAcct == account_)
280 {
281 if (auto const ter = addEmptyHolding(
282 view(), account_, mPriorBalance, vaultAsset, j_);
283 !isTesSuccess(ter) && ter != tecDUPLICATE)
284 return ter;
285 }
286
287 // Transfer assets from vault to depositor or destination account.
288 if (auto const ter = accountSend(
289 view(),
290 vaultAccount,
291 dstAcct,
292 assetsWithdrawn,
293 j_,
295 !isTesSuccess(ter))
296 return ter;
297
298 // Sanity check
299 if (accountHolds(
300 view(),
301 vaultAccount,
302 assetsWithdrawn.asset(),
305 j_) < beast::zero)
306 {
307 // LCOV_EXCL_START
308 JLOG(j_.error()) << "VaultWithdraw: negative balance of vault assets.";
309 return tefINTERNAL;
310 // LCOV_EXCL_STOP
311 }
312
313 return tesSUCCESS;
314}
315
316} // 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