rippled
Loading...
Searching...
No Matches
VaultClawback.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2025 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/tx/detail/VaultClawback.h>
21#include <xrpld/ledger/View.h>
22
23#include <xrpl/beast/utility/instrumentation.h>
24#include <xrpl/protocol/Feature.h>
25#include <xrpl/protocol/MPTIssue.h>
26#include <xrpl/protocol/STAmount.h>
27#include <xrpl/protocol/STNumber.h>
28#include <xrpl/protocol/TER.h>
29#include <xrpl/protocol/TxFlags.h>
30
31namespace ripple {
32
35{
36 if (!ctx.rules.enabled(featureSingleAssetVault))
37 return temDISABLED;
38
39 if (auto const ter = preflight1(ctx))
40 return ter;
41
42 if (ctx.tx.getFlags() & tfUniversalMask)
43 return temINVALID_FLAG;
44
45 if (ctx.tx[sfVaultID] == beast::zero)
46 {
47 JLOG(ctx.j.debug()) << "VaultClawback: zero/empty vault ID.";
48 return temMALFORMED;
49 }
50
51 AccountID const issuer = ctx.tx[sfAccount];
52 AccountID const holder = ctx.tx[sfHolder];
53
54 if (issuer == holder)
55 {
56 JLOG(ctx.j.debug()) << "VaultClawback: issuer cannot be holder.";
57 return temMALFORMED;
58 }
59
60 auto const amount = ctx.tx[~sfAmount];
61 if (amount)
62 {
63 // Note, zero amount is valid, it means "all". It is also the default.
64 if (*amount < beast::zero)
65 return temBAD_AMOUNT;
66 else if (isXRP(amount->asset()))
67 {
68 JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback XRP.";
69 return temMALFORMED;
70 }
71 else if (amount->asset().getIssuer() != issuer)
72 {
73 JLOG(ctx.j.debug())
74 << "VaultClawback: only asset issuer can clawback.";
75 return temMALFORMED;
76 }
77 }
78
79 return preflight2(ctx);
80}
81
82TER
84{
85 auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
86 if (!vault)
87 return tecNO_ENTRY;
88
89 auto account = ctx.tx[sfAccount];
90 auto const issuer = ctx.view.read(keylet::account(account));
91 if (!issuer)
92 {
93 // LCOV_EXCL_START
94 JLOG(ctx.j.error()) << "VaultClawback: missing issuer account.";
95 return tefINTERNAL;
96 // LCOV_EXCL_STOP
97 }
98
99 Asset const vaultAsset = vault->at(sfAsset);
100 if (auto const amount = ctx.tx[~sfAmount];
101 amount && vaultAsset != amount->asset())
102 return tecWRONG_ASSET;
103
104 if (vaultAsset.native())
105 {
106 JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback XRP.";
107 return tecNO_PERMISSION; // Cannot clawback XRP.
108 }
109 else if (vaultAsset.getIssuer() != account)
110 {
111 JLOG(ctx.j.debug()) << "VaultClawback: only asset issuer can clawback.";
112 return tecNO_PERMISSION; // Only issuers can clawback.
113 }
114
115 if (vaultAsset.holds<MPTIssue>())
116 {
117 auto const mpt = vaultAsset.get<MPTIssue>();
118 auto const mptIssue =
119 ctx.view.read(keylet::mptIssuance(mpt.getMptID()));
120 if (mptIssue == nullptr)
121 return tecOBJECT_NOT_FOUND;
122
123 std::uint32_t const issueFlags = mptIssue->getFieldU32(sfFlags);
124 if (!(issueFlags & lsfMPTCanClawback))
125 {
126 JLOG(ctx.j.debug())
127 << "VaultClawback: cannot clawback MPT vault asset.";
128 return tecNO_PERMISSION;
129 }
130 }
131 else if (vaultAsset.holds<Issue>())
132 {
133 std::uint32_t const issuerFlags = issuer->getFieldU32(sfFlags);
134 if (!(issuerFlags & lsfAllowTrustLineClawback) ||
135 (issuerFlags & lsfNoFreeze))
136 {
137 JLOG(ctx.j.debug())
138 << "VaultClawback: cannot clawback IOU vault asset.";
139 return tecNO_PERMISSION;
140 }
141 }
142
143 return tesSUCCESS;
144}
145
146TER
148{
149 auto const& tx = ctx_.tx;
150 auto const vault = view().peek(keylet::vault(tx[sfVaultID]));
151 if (!vault)
152 return tefINTERNAL; // LCOV_EXCL_LINE
153
154 auto const mptIssuanceID = (*vault)[sfShareMPTID];
155 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
156 if (!sleIssuance)
157 {
158 // LCOV_EXCL_START
159 JLOG(j_.error()) << "VaultClawback: missing issuance of vault shares.";
160 return tefINTERNAL;
161 // LCOV_EXCL_STOP
162 }
163
164 Asset const asset = vault->at(sfAsset);
165 STAmount const amount = [&]() -> STAmount {
166 auto const maybeAmount = tx[~sfAmount];
167 if (maybeAmount)
168 return *maybeAmount;
169 return {sfAmount, asset, 0};
170 }();
171 XRPL_ASSERT(
172 amount.asset() == asset,
173 "ripple::VaultClawback::doApply : matching asset");
174
175 AccountID holder = tx[sfHolder];
176 STAmount assets, shares;
177 if (amount == beast::zero)
178 {
179 Asset share = *(*vault)[sfShareMPTID];
180 shares = accountHolds(
181 view(),
182 holder,
183 share,
186 j_);
187 assets = sharesToAssetsWithdraw(vault, sleIssuance, shares);
188 }
189 else
190 {
191 assets = amount;
192 shares = assetsToSharesWithdraw(vault, sleIssuance, assets);
193 }
194
195 // Clamp to maximum.
196 Number maxAssets = *vault->at(sfAssetsAvailable);
197 if (assets > maxAssets)
198 {
199 assets = maxAssets;
200 shares = assetsToSharesWithdraw(vault, sleIssuance, assets);
201 }
202
203 if (shares == beast::zero)
205
206 vault->at(sfAssetsTotal) -= assets;
207 vault->at(sfAssetsAvailable) -= assets;
208 view().update(vault);
209
210 auto const& vaultAccount = vault->at(sfAccount);
211 // Transfer shares from holder to vault.
212 if (auto ter = accountSend(
213 view(), holder, vaultAccount, shares, j_, WaiveTransferFee::Yes))
214 return ter;
215
216 // Transfer assets from vault to issuer.
217 if (auto ter = accountSend(
218 view(), vaultAccount, account_, assets, j_, WaiveTransferFee::Yes))
219 return ter;
220
221 // Sanity check
222 if (accountHolds(
223 view(),
224 vaultAccount,
225 assets.asset(),
228 j_) < beast::zero)
229 {
230 // LCOV_EXCL_START
231 JLOG(j_.error()) << "VaultClawback: negative balance of vault assets.";
232 return tefINTERNAL;
233 // LCOV_EXCL_STOP
234 }
235
236 return tesSUCCESS;
237}
238
239} // namespace ripple
Stream error() const
Definition Journal.h:346
Stream debug() const
Definition Journal.h:328
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:101
constexpr TIss const & get() const
AccountID const & getIssuer() const
Definition Asset.cpp:36
constexpr bool holds() const
Definition Asset.h:132
A currency issued by an account.
Definition Issue.h:33
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:130
Asset const & asset() const
Definition STAmount.h:483
std::uint32_t getFlags() const
Definition STObject.cpp:537
AccountID const account_
Definition Transactor.h:143
ApplyView & view()
Definition Transactor.h:159
beast::Journal const j_
Definition Transactor.h:141
ApplyContext & ctx_
Definition Transactor.h:140
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:526
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:564
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
@ fhIGNORE_FREEZE
Definition View.h:78
bool isXRP(AccountID const &c)
Definition AccountID.h:90
STAmount sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:2834
@ lsfAllowTrustLineClawback
@ lsfMPTCanClawback
@ ahIGNORE_AUTH
Definition View.h:81
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
@ tefINTERNAL
Definition TER.h:173
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
@ tecNO_ENTRY
Definition TER.h:306
@ tecOBJECT_NOT_FOUND
Definition TER.h:326
@ tecINSUFFICIENT_FUNDS
Definition TER.h:325
@ tecNO_PERMISSION
Definition TER.h:305
@ tecWRONG_ASSET
Definition TER.h:360
@ tesSUCCESS
Definition TER.h:244
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:385
STAmount assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition View.cpp:2815
constexpr std::uint32_t tfUniversalMask
Definition TxFlags.h:63
TER accountSend(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2114
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
@ temBAD_AMOUNT
Definition TER.h:89
@ temMALFORMED
Definition TER.h:87
@ temINVALID_FLAG
Definition TER.h:111
@ temDISABLED
Definition TER.h:114
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:79
ReadView const & view
Definition Transactor.h:82
beast::Journal const j
Definition Transactor.h:87
State information when preflighting a tx.
Definition Transactor.h:34
beast::Journal const j
Definition Transactor.h:41