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/Feature.h>
7#include <xrpl/protocol/MPTIssue.h>
8#include <xrpl/protocol/SField.h>
9#include <xrpl/protocol/STAmount.h>
10#include <xrpl/protocol/STNumber.h>
11#include <xrpl/protocol/TER.h>
12#include <xrpl/protocol/TxFlags.h>
13
14namespace ripple {
15
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 AccountID const issuer = ctx.tx[sfAccount];
26 AccountID const holder = ctx.tx[sfHolder];
27
28 if (issuer == holder)
29 {
30 JLOG(ctx.j.debug()) << "VaultClawback: issuer cannot be holder.";
31 return temMALFORMED;
32 }
33
34 auto const amount = ctx.tx[~sfAmount];
35 if (amount)
36 {
37 // Note, zero amount is valid, it means "all". It is also the default.
38 if (*amount < beast::zero)
39 return temBAD_AMOUNT;
40 else if (isXRP(amount->asset()))
41 {
42 JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback XRP.";
43 return temMALFORMED;
44 }
45 else if (amount->asset().getIssuer() != issuer)
46 {
47 JLOG(ctx.j.debug())
48 << "VaultClawback: only asset issuer can clawback.";
49 return temMALFORMED;
50 }
51 }
52
53 return tesSUCCESS;
54}
55
56TER
58{
59 auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
60 if (!vault)
61 return tecNO_ENTRY;
62
63 auto account = ctx.tx[sfAccount];
64 auto const issuer = ctx.view.read(keylet::account(account));
65 if (!issuer)
66 {
67 // LCOV_EXCL_START
68 JLOG(ctx.j.error()) << "VaultClawback: missing issuer account.";
69 return tefINTERNAL;
70 // LCOV_EXCL_STOP
71 }
72
73 Asset const vaultAsset = vault->at(sfAsset);
74 if (auto const amount = ctx.tx[~sfAmount];
75 amount && vaultAsset != amount->asset())
76 return tecWRONG_ASSET;
77
78 if (vaultAsset.native())
79 {
80 JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback XRP.";
81 return tecNO_PERMISSION; // Cannot clawback XRP.
82 }
83 else if (vaultAsset.getIssuer() != account)
84 {
85 JLOG(ctx.j.debug()) << "VaultClawback: only asset issuer can clawback.";
86 return tecNO_PERMISSION; // Only issuers can clawback.
87 }
88
89 if (vaultAsset.holds<MPTIssue>())
90 {
91 auto const mpt = vaultAsset.get<MPTIssue>();
92 auto const mptIssue =
93 ctx.view.read(keylet::mptIssuance(mpt.getMptID()));
94 if (mptIssue == nullptr)
96
97 std::uint32_t const issueFlags = mptIssue->getFieldU32(sfFlags);
98 if (!(issueFlags & lsfMPTCanClawback))
99 {
100 JLOG(ctx.j.debug())
101 << "VaultClawback: cannot clawback MPT vault asset.";
102 return tecNO_PERMISSION;
103 }
104 }
105 else if (vaultAsset.holds<Issue>())
106 {
107 std::uint32_t const issuerFlags = issuer->getFieldU32(sfFlags);
108 if (!(issuerFlags & lsfAllowTrustLineClawback) ||
109 (issuerFlags & lsfNoFreeze))
110 {
111 JLOG(ctx.j.debug())
112 << "VaultClawback: cannot clawback IOU vault asset.";
113 return tecNO_PERMISSION;
114 }
115 }
116
117 return tesSUCCESS;
118}
119
120TER
122{
123 auto const& tx = ctx_.tx;
124 auto const vault = view().peek(keylet::vault(tx[sfVaultID]));
125 if (!vault)
126 return tefINTERNAL; // LCOV_EXCL_LINE
127
128 auto const mptIssuanceID = *((*vault)[sfShareMPTID]);
129 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
130 if (!sleIssuance)
131 {
132 // LCOV_EXCL_START
133 JLOG(j_.error()) << "VaultClawback: missing issuance of vault shares.";
134 return tefINTERNAL;
135 // LCOV_EXCL_STOP
136 }
137
138 Asset const vaultAsset = vault->at(sfAsset);
139 STAmount const amount = [&]() -> STAmount {
140 auto const maybeAmount = tx[~sfAmount];
141 if (maybeAmount)
142 return *maybeAmount;
143 return {sfAmount, vaultAsset, 0};
144 }();
145 XRPL_ASSERT(
146 amount.asset() == vaultAsset,
147 "ripple::VaultClawback::doApply : matching asset");
148
149 auto assetsAvailable = vault->at(sfAssetsAvailable);
150 auto assetsTotal = vault->at(sfAssetsTotal);
151 [[maybe_unused]] auto const lossUnrealized = vault->at(sfLossUnrealized);
152 XRPL_ASSERT(
153 lossUnrealized <= (assetsTotal - assetsAvailable),
154 "ripple::VaultClawback::doApply : loss and assets do balance");
155
156 AccountID holder = tx[sfHolder];
157 MPTIssue const share{mptIssuanceID};
158 STAmount sharesDestroyed = {share};
159 STAmount assetsRecovered;
160 try
161 {
162 if (amount == beast::zero)
163 {
164 sharesDestroyed = accountHolds(
165 view(),
166 holder,
167 share,
170 j_);
171
172 auto const maybeAssets =
173 sharesToAssetsWithdraw(vault, sleIssuance, sharesDestroyed);
174 if (!maybeAssets)
175 return tecINTERNAL; // LCOV_EXCL_LINE
176 assetsRecovered = *maybeAssets;
177 }
178 else
179 {
180 assetsRecovered = amount;
181 {
182 auto const maybeShares =
183 assetsToSharesWithdraw(vault, sleIssuance, assetsRecovered);
184 if (!maybeShares)
185 return tecINTERNAL; // LCOV_EXCL_LINE
186 sharesDestroyed = *maybeShares;
187 }
188
189 auto const maybeAssets =
190 sharesToAssetsWithdraw(vault, sleIssuance, sharesDestroyed);
191 if (!maybeAssets)
192 return tecINTERNAL; // LCOV_EXCL_LINE
193 assetsRecovered = *maybeAssets;
194 }
195
196 // Clamp to maximum.
197 if (assetsRecovered > *assetsAvailable)
198 {
199 assetsRecovered = *assetsAvailable;
200 // Note, it is important to truncate the number of shares, otherwise
201 // the corresponding assets might breach the AssetsAvailable
202 {
203 auto const maybeShares = assetsToSharesWithdraw(
204 vault, sleIssuance, assetsRecovered, TruncateShares::yes);
205 if (!maybeShares)
206 return tecINTERNAL; // LCOV_EXCL_LINE
207 sharesDestroyed = *maybeShares;
208 }
209
210 auto const maybeAssets =
211 sharesToAssetsWithdraw(vault, sleIssuance, sharesDestroyed);
212 if (!maybeAssets)
213 return tecINTERNAL; // LCOV_EXCL_LINE
214 assetsRecovered = *maybeAssets;
215 if (assetsRecovered > *assetsAvailable)
216 {
217 // LCOV_EXCL_START
218 JLOG(j_.error())
219 << "VaultClawback: invalid rounding of shares.";
220 return tecINTERNAL;
221 // LCOV_EXCL_STOP
222 }
223 }
224 }
225 catch (std::overflow_error const&)
226 {
227 // It's easy to hit this exception from Number with large enough Scale
228 // so we avoid spamming the log and only use debug here.
229 JLOG(j_.debug()) //
230 << "VaultClawback: overflow error with"
231 << " scale=" << (int)vault->at(sfScale).value() //
232 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
233 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
234 << ", amount=" << amount.value();
235 return tecPATH_DRY;
236 }
237
238 if (sharesDestroyed == beast::zero)
239 return tecPRECISION_LOSS;
240
241 assetsTotal -= assetsRecovered;
242 assetsAvailable -= assetsRecovered;
243 view().update(vault);
244
245 auto const& vaultAccount = vault->at(sfAccount);
246 // Transfer shares from holder to vault.
247 if (auto const ter = accountSend(
248 view(),
249 holder,
250 vaultAccount,
251 sharesDestroyed,
252 j_,
254 !isTesSuccess(ter))
255 return ter;
256
257 // Try to remove MPToken for shares, if the holder balance is zero. Vault
258 // pseudo-account will never set lsfMPTAuthorized, so we ignore flags.
259 // Keep MPToken if holder is the vault owner.
260 if (holder != vault->at(sfOwner))
261 {
262 if (auto const ter =
263 removeEmptyHolding(view(), holder, sharesDestroyed.asset(), j_);
264 isTesSuccess(ter))
265 {
266 JLOG(j_.debug()) //
267 << "VaultClawback: removed empty MPToken for vault shares"
268 << " MPTID=" << to_string(mptIssuanceID) //
269 << " account=" << toBase58(holder);
270 }
271 else if (ter != tecHAS_OBLIGATIONS)
272 {
273 // LCOV_EXCL_START
274 JLOG(j_.error()) //
275 << "VaultClawback: failed to remove MPToken for vault shares"
276 << " MPTID=" << to_string(mptIssuanceID) //
277 << " account=" << toBase58(holder) //
278 << " with result: " << transToken(ter);
279 return ter;
280 // LCOV_EXCL_STOP
281 }
282 // else quietly ignore, holder balance is not zero
283 }
284
285 // Transfer assets from vault to issuer.
286 if (auto const ter = accountSend(
287 view(),
288 vaultAccount,
289 account_,
290 assetsRecovered,
291 j_,
293 !isTesSuccess(ter))
294 return ter;
295
296 // Sanity check
297 if (accountHolds(
298 view(),
299 vaultAccount,
300 assetsRecovered.asset(),
303 j_) < beast::zero)
304 {
305 // LCOV_EXCL_START
306 JLOG(j_.error()) << "VaultClawback: negative balance of vault assets.";
307 return tefINTERNAL;
308 // LCOV_EXCL_STOP
309 }
310
311 return tesSUCCESS;
312}
313
314} // 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
constexpr TIss const & get() const
AccountID const & getIssuer() const
Definition Asset.cpp:17
constexpr bool holds() const
Definition Asset.h:113
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.
Asset const & asset() const
Definition STAmount.h:464
STAmount const & value() const noexcept
Definition STAmount.h:575
AccountID const account_
Definition Transactor.h:128
ApplyView & view()
Definition Transactor.h:144
beast::Journal const j_
Definition Transactor.h:126
ApplyContext & ctx_
Definition Transactor.h:124
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext 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
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
@ fhIGNORE_FREEZE
Definition View.h:58
bool isXRP(AccountID const &c)
Definition AccountID.h:71
@ lsfAllowTrustLineClawback
@ lsfMPTCanClawback
@ 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
@ 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
@ tecOBJECT_NOT_FOUND
Definition TER.h:308
@ tecINTERNAL
Definition TER.h:292
@ tecNO_PERMISSION
Definition TER.h:287
@ tecPRECISION_LOSS
Definition TER.h:345
@ tecHAS_OBLIGATIONS
Definition TER.h:299
@ tecWRONG_ASSET
Definition TER.h:342
@ tecPATH_DRY
Definition TER.h:276
@ tesSUCCESS
Definition TER.h:226
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
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