rippled
Loading...
Searching...
No Matches
VaultClawback.cpp
1#include <xrpld/app/tx/detail/VaultClawback.h>
2//
3#include <xrpl/beast/utility/instrumentation.h>
4#include <xrpl/ledger/View.h>
5#include <xrpl/protocol/AccountID.h>
6#include <xrpl/protocol/MPTIssue.h>
7#include <xrpl/protocol/SField.h>
8#include <xrpl/protocol/STAmount.h>
9#include <xrpl/protocol/STNumber.h>
10#include <xrpl/protocol/STTakesAsset.h>
11#include <xrpl/protocol/TER.h>
12
13#include <optional>
14
15namespace xrpl {
18{
19 if (ctx.tx[sfVaultID] == beast::zero)
20 {
21 JLOG(ctx.j.debug()) << "VaultClawback: zero/empty vault ID.";
22 return temMALFORMED;
23 }
24
25 auto const amount = ctx.tx[~sfAmount];
26 if (amount)
27 {
28 // Note, zero amount is valid, it means "all". It is also the default.
29 if (*amount < beast::zero)
30 return temBAD_AMOUNT;
31 else if (isXRP(amount->asset()))
32 {
33 JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback XRP.";
34 return temMALFORMED;
35 }
36 }
37
38 return tesSUCCESS;
39}
40
41[[nodiscard]] STAmount
43 std::shared_ptr<SLE const> const& vault,
44 std::optional<STAmount> const& maybeAmount,
45 AccountID const& account)
46{
47 if (maybeAmount)
48 return *maybeAmount;
49
50 Asset const share = MPTIssue{vault->at(sfShareMPTID)};
51 if (account == vault->at(sfOwner))
52 return STAmount{share};
53
54 return STAmount{vault->at(sfAsset)};
55}
56
57TER
59{
60 auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
61 if (!vault)
62 return tecNO_ENTRY;
63
64 Asset const vaultAsset = vault->at(sfAsset);
65 auto const account = ctx.tx[sfAccount];
66 auto const holder = ctx.tx[sfHolder];
67 auto const maybeAmount = ctx.tx[~sfAmount];
68 auto const mptIssuanceID = vault->at(sfShareMPTID);
69 auto const sleShareIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
70 if (!sleShareIssuance)
71 {
72 // LCOV_EXCL_START
73 JLOG(ctx.j.error()) << "VaultClawback: missing issuance of vault shares.";
74 return tefINTERNAL;
75 // LCOV_EXCL_STOP
76 }
77
78 Asset const share = MPTIssue{mptIssuanceID};
79
80 // Ambiguous case: If Issuer is Owner they must specify the asset
81 if (!maybeAmount && !vaultAsset.native() && vaultAsset.getIssuer() == vault->at(sfOwner))
82 {
83 JLOG(ctx.j.debug()) << "VaultClawback: must specify amount when issuer is owner.";
84 return tecWRONG_ASSET;
85 }
86
87 auto const amount = clawbackAmount(vault, maybeAmount, account);
88
89 // There is a special case that allows the VaultOwner to use clawback to
90 // burn shares when Vault assets total and available are zero, but
91 // shares remain. However, that case is handled in doApply() directly,
92 // so here we just enforce checks.
93 if (amount.asset() == share)
94 {
95 // Only the Vault Owner may clawback shares
96 if (account != vault->at(sfOwner))
97 {
98 JLOG(ctx.j.debug()) << "VaultClawback: only vault owner can clawback shares.";
99 return tecNO_PERMISSION;
100 }
101
102 auto const assetsTotal = vault->at(sfAssetsTotal);
103 auto const assetsAvailable = vault->at(sfAssetsAvailable);
104 auto const sharesTotal = sleShareIssuance->at(sfOutstandingAmount);
105
106 // Owner can clawback funds when the vault has shares but no assets
107 if (sharesTotal == 0 || (assetsTotal != 0 || assetsAvailable != 0))
108 {
109 JLOG(ctx.j.debug()) << "VaultClawback: vault owner can clawback shares only"
110 " when vault has no assets.";
111 return tecNO_PERMISSION;
112 }
113
114 // If amount is non-zero, the VaultOwner must burn all shares
115 if (amount != beast::zero)
116 {
117 Number const& sharesHeld = accountHolds(
119
120 // The VaultOwner must burn all shares
121 if (amount != sharesHeld)
122 {
123 JLOG(ctx.j.debug()) << "VaultClawback: vault owner must clawback all "
124 "shares.";
125 return tecLIMIT_EXCEEDED;
126 }
127 }
128
129 return tesSUCCESS;
130 }
131
132 // The asset that is being clawed back is the vault asset
133 if (amount.asset() == vaultAsset)
134 {
135 // XRP cannot be clawed back
136 if (vaultAsset.native())
137 {
138 JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback XRP.";
139 return tecNO_PERMISSION;
140 }
141
142 // Only the Asset Issuer may clawback the asset
143 if (account != vaultAsset.getIssuer())
144 {
145 JLOG(ctx.j.debug()) << "VaultClawback: only asset issuer can clawback asset.";
146 return tecNO_PERMISSION;
147 }
148
149 // The issuer cannot clawback from itself
150 if (account == holder)
151 {
152 JLOG(ctx.j.debug()) << "VaultClawback: issuer cannot be the holder.";
153 return tecNO_PERMISSION;
154 }
155
156 return std::visit(
157 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
158 if constexpr (std::is_same_v<TIss, MPTIssue>)
159 {
160 auto const mptIssue = ctx.view.read(keylet::mptIssuance(issue.getMptID()));
161 if (mptIssue == nullptr)
162 return tecOBJECT_NOT_FOUND;
163
164 std::uint32_t const issueFlags = mptIssue->getFieldU32(sfFlags);
165 if (!(issueFlags & lsfMPTCanClawback))
166 {
167 JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback "
168 "MPT vault asset.";
169 return tecNO_PERMISSION;
170 }
171 }
172 else if constexpr (std::is_same_v<TIss, Issue>)
173 {
174 auto const issuerSle = ctx.view.read(keylet::account(account));
175 if (!issuerSle)
176 {
177 // LCOV_EXCL_START
178 JLOG(ctx.j.error()) << "VaultClawback: missing submitter account.";
179 return tefINTERNAL;
180 // LCOV_EXCL_STOP
181 }
182
183 std::uint32_t const issuerFlags = issuerSle->getFieldU32(sfFlags);
184 if (!(issuerFlags & lsfAllowTrustLineClawback) || (issuerFlags & lsfNoFreeze))
185 {
186 JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback "
187 "IOU vault asset.";
188 return tecNO_PERMISSION;
189 }
190 }
191 return tesSUCCESS;
192 },
193 vaultAsset.value());
194 }
195
196 // Invalid asset
197 return tecWRONG_ASSET;
198}
199
202 std::shared_ptr<SLE> const& vault,
203 std::shared_ptr<SLE const> const& sleShareIssuance,
204 AccountID const& holder,
206{
207 if (clawbackAmount.asset() != vault->at(sfAsset))
208 {
209 // preclaim should have blocked this , now it's an internal error
210 // LCOV_EXCL_START
211 JLOG(j_.error()) << "VaultClawback: asset mismatch in clawback.";
212 return Unexpected(tecINTERNAL);
213 // LCOV_EXCL_STOP
214 }
215
216 auto const assetsAvailable = vault->at(sfAssetsAvailable);
217 auto const mptIssuanceID = *vault->at(sfShareMPTID);
218 MPTIssue const share{mptIssuanceID};
219
220 if (clawbackAmount == beast::zero)
221 {
222 auto const sharesDestroyed =
224 auto const maybeAssets = sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed);
225 if (!maybeAssets)
226 return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
227
228 return std::make_pair(*maybeAssets, sharesDestroyed);
229 }
230
231 STAmount sharesDestroyed;
232 STAmount assetsRecovered = clawbackAmount;
233 try
234 {
235 {
236 auto const maybeShares = assetsToSharesWithdraw(vault, sleShareIssuance, assetsRecovered);
237 if (!maybeShares)
238 return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
239 sharesDestroyed = *maybeShares;
240 }
241
242 auto const maybeAssets = sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed);
243 if (!maybeAssets)
244 return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
245 assetsRecovered = *maybeAssets;
246
247 // Clamp to maximum.
248 if (assetsRecovered > *assetsAvailable)
249 {
250 assetsRecovered = *assetsAvailable;
251 // Note, it is important to truncate the number of shares,
252 // otherwise the corresponding assets might breach the
253 // AssetsAvailable
254 {
255 auto const maybeShares =
256 assetsToSharesWithdraw(vault, sleShareIssuance, assetsRecovered, TruncateShares::yes);
257 if (!maybeShares)
258 return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
259 sharesDestroyed = *maybeShares;
260 }
261
262 auto const maybeAssets = sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed);
263 if (!maybeAssets)
264 return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
265 assetsRecovered = *maybeAssets;
266 if (assetsRecovered > *assetsAvailable)
267 {
268 // LCOV_EXCL_START
269 JLOG(j_.error()) << "VaultClawback: invalid rounding of shares.";
270 return Unexpected(tecINTERNAL);
271 // LCOV_EXCL_STOP
272 }
273 }
274 }
275 catch (std::overflow_error const&)
276 {
277 // It's easy to hit this exception from Number with large enough
278 // Scale so we avoid spamming the log and only use debug here.
279 JLOG(j_.debug()) //
280 << "VaultClawback: overflow error with"
281 << " scale=" << (int)vault->at(sfScale).value() //
282 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
283 << ", sharesTotal=" << sleShareIssuance->at(sfOutstandingAmount) << ", amount=" << clawbackAmount.value();
284 return Unexpected(tecPATH_DRY);
285 }
286
287 return std::make_pair(assetsRecovered, sharesDestroyed);
288}
289
290TER
292{
293 auto const& tx = ctx_.tx;
294 auto const vault = view().peek(keylet::vault(tx[sfVaultID]));
295 if (!vault)
296 return tefINTERNAL; // LCOV_EXCL_LINE
297
298 auto const mptIssuanceID = *vault->at(sfShareMPTID);
299 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
300 if (!sleIssuance)
301 {
302 // LCOV_EXCL_START
303 JLOG(j_.error()) << "VaultClawback: missing issuance of vault shares.";
304 return tefINTERNAL;
305 // LCOV_EXCL_STOP
306 }
307 MPTIssue const share{mptIssuanceID};
308
309 Asset const vaultAsset = vault->at(sfAsset);
310 STAmount const amount = clawbackAmount(vault, tx[~sfAmount], account_);
311
312 auto assetsAvailable = vault->at(sfAssetsAvailable);
313 auto assetsTotal = vault->at(sfAssetsTotal);
314
315 [[maybe_unused]] auto const lossUnrealized = vault->at(sfLossUnrealized);
316 XRPL_ASSERT(
317 lossUnrealized <= (assetsTotal - assetsAvailable), "xrpl::VaultClawback::doApply : loss and assets do balance");
318
319 AccountID holder = tx[sfHolder];
320 STAmount sharesDestroyed = {share};
321 STAmount assetsRecovered = {vault->at(sfAsset)};
322
323 // The Owner is burning shares
324 if (account_ == vault->at(sfOwner) && amount.asset() == share)
325 {
326 sharesDestroyed =
328 }
329 else // The Issuer is clawbacking vault assets
330 {
331 XRPL_ASSERT(amount.asset() == vaultAsset, "xrpl::VaultClawback::doApply : matching asset");
332
333 auto const clawbackParts = assetsToClawback(vault, sleIssuance, holder, amount);
334 if (!clawbackParts)
335 return clawbackParts.error();
336
337 assetsRecovered = clawbackParts->first;
338 sharesDestroyed = clawbackParts->second;
339 }
340
341 if (sharesDestroyed == beast::zero)
342 return tecPRECISION_LOSS;
343
344 assetsTotal -= assetsRecovered;
345 assetsAvailable -= assetsRecovered;
346 view().update(vault);
347
348 auto const& vaultAccount = vault->at(sfAccount);
349 // Transfer shares from holder to vault.
350 if (auto const ter = accountSend(view(), holder, vaultAccount, sharesDestroyed, j_, WaiveTransferFee::Yes);
351 !isTesSuccess(ter))
352 return ter;
353
354 // Try to remove MPToken for shares, if the holder balance is zero. Vault
355 // pseudo-account will never set lsfMPTAuthorized, so we ignore flags.
356 // Keep MPToken if holder is the vault owner.
357 if (holder != vault->at(sfOwner))
358 {
359 if (auto const ter = removeEmptyHolding(view(), holder, sharesDestroyed.asset(), j_); isTesSuccess(ter))
360 {
361 JLOG(j_.debug()) //
362 << "VaultClawback: removed empty MPToken for vault shares"
363 << " MPTID=" << to_string(mptIssuanceID) //
364 << " account=" << toBase58(holder);
365 }
366 else if (ter != tecHAS_OBLIGATIONS)
367 {
368 // LCOV_EXCL_START
369 JLOG(j_.error()) //
370 << "VaultClawback: failed to remove MPToken for vault shares"
371 << " MPTID=" << to_string(mptIssuanceID) //
372 << " account=" << toBase58(holder) //
373 << " with result: " << transToken(ter);
374 return ter;
375 // LCOV_EXCL_STOP
376 }
377 // else quietly ignore, holder balance is not zero
378 }
379
380 if (assetsRecovered > beast::zero)
381 {
382 // Transfer assets from vault to issuer.
383 if (auto const ter = accountSend(view(), vaultAccount, account_, assetsRecovered, j_, WaiveTransferFee::Yes);
384 !isTesSuccess(ter))
385 return ter;
386
387 // Sanity check
388 if (accountHolds(
389 view(),
390 vaultAccount,
391 assetsRecovered.asset(),
394 j_) < beast::zero)
395 {
396 // LCOV_EXCL_START
397 JLOG(j_.error()) << "VaultClawback: negative balance of vault assets.";
398 return tefINTERNAL;
399 // LCOV_EXCL_STOP
400 }
401 }
402
403 associateAsset(*vault, vaultAsset);
404
405 return tesSUCCESS;
406}
407
408} // namespace xrpl
Stream error() const
Definition Journal.h:318
Stream debug() const
Definition Journal.h:300
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.
AccountID const & getIssuer() const
Definition Asset.cpp:17
bool native() const
Definition Asset.h:79
constexpr value_type const & value() const
Definition Asset.h:154
Number is a floating point type that can represent a wide range of values.
Definition Number.h:207
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
Asset const & asset() const
Definition STAmount.h:441
STAmount const & value() const noexcept
Definition STAmount.h:560
AccountID const account_
Definition Transactor.h:112
beast::Journal const j_
Definition Transactor.h:110
ApplyView & view()
Definition Transactor.h:128
ApplyContext & ctx_
Definition Transactor.h:108
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
Expected< std::pair< STAmount, STAmount >, TER > assetsToClawback(std::shared_ptr< SLE > const &vault, std::shared_ptr< SLE const > const &sleShareIssuance, AccountID const &holder, STAmount const &clawbackAmount)
TER doApply() override
T is_same_v
T make_pair(T... args)
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:462
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:492
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:160
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ fhIGNORE_FREEZE
Definition View.h:58
bool isXRP(AccountID const &c)
Definition AccountID.h:70
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1554
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:597
@ 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)
Definition View.cpp:392
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.
Definition View.cpp:2446
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:3149
TERSubset< CanCvtToTER > TER
Definition TER.h:620
@ ahIGNORE_AUTH
Definition View.h:61
@ temMALFORMED
Definition TER.h:67
@ temBAD_AMOUNT
Definition TER.h:69
bool isTesSuccess(TER x) noexcept
Definition TER.h:649
std::optional< STAmount > sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3174
@ tecWRONG_ASSET
Definition TER.h:341
@ tecNO_ENTRY
Definition TER.h:287
@ tecPATH_DRY
Definition TER.h:275
@ tecOBJECT_NOT_FOUND
Definition TER.h:307
@ tecINTERNAL
Definition TER.h:291
@ tecPRECISION_LOSS
Definition TER.h:344
@ tecLIMIT_EXCEEDED
Definition TER.h:342
@ tecNO_PERMISSION
Definition TER.h:286
@ tecHAS_OBLIGATIONS
Definition TER.h:298
@ lsfAllowTrustLineClawback
@ lsfNoFreeze
@ lsfMPTCanClawback
void associateAsset(STLedgerEntry &sle, Asset const &asset)
Associate an Asset with all sMD_NeedsAsset fields in a ledger entry.
STAmount clawbackAmount(std::shared_ptr< SLE const > const &vault, std::optional< STAmount > const &maybeAmount, AccountID const &account)
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:580
@ tesSUCCESS
Definition TER.h:225
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:53
ReadView const & view
Definition Transactor.h:56
beast::Journal const j
Definition Transactor.h:61
State information when preflighting a tx.
Definition Transactor.h:15
beast::Journal const j
Definition Transactor.h:22
T visit(T... args)