rippled
Loading...
Searching...
No Matches
EscrowFinish.cpp
1#include <xrpl/basics/Log.h>
2#include <xrpl/basics/chrono.h>
3#include <xrpl/conditions/Condition.h>
4#include <xrpl/conditions/Fulfillment.h>
5#include <xrpl/core/HashRouter.h>
6#include <xrpl/ledger/ApplyView.h>
7#include <xrpl/ledger/View.h>
8#include <xrpl/ledger/helpers/CredentialHelpers.h>
9#include <xrpl/ledger/helpers/MPTokenHelpers.h>
10#include <xrpl/ledger/helpers/RippleStateHelpers.h>
11#include <xrpl/protocol/Feature.h>
12#include <xrpl/protocol/Indexes.h>
13#include <xrpl/protocol/Rate.h>
14#include <xrpl/protocol/TxFlags.h>
15#include <xrpl/protocol/XRPAmount.h>
16#include <xrpl/tx/transactors/escrow/EscrowFinish.h>
17
18#include <libxrpl/tx/transactors/escrow/EscrowHelpers.h>
19
20namespace xrpl {
21
22// During an EscrowFinish, the transaction must specify both
23// a condition and a fulfillment. We track whether that
24// fulfillment matches and validates the condition.
27
28//------------------------------------------------------------------------------
29
30static bool
32{
33 using namespace xrpl::cryptoconditions;
34
36
37 auto condition = Condition::deserialize(c, ec);
38 if (!condition)
39 return false;
40
41 auto fulfillment = Fulfillment::deserialize(f, ec);
42 if (!fulfillment)
43 return false;
44
45 return validate(*fulfillment, *condition);
46}
47
48bool
50{
51 return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials);
52}
53
56{
57 auto const cb = ctx.tx[~sfCondition];
58 auto const fb = ctx.tx[~sfFulfillment];
59
60 // If you specify a condition, then you must also specify
61 // a fulfillment.
62 if (static_cast<bool>(cb) != static_cast<bool>(fb))
63 return temMALFORMED;
64
65 return tesSUCCESS;
66}
67
70{
71 auto const cb = ctx.tx[~sfCondition];
72 auto const fb = ctx.tx[~sfFulfillment];
73
74 if (cb && fb)
75 {
76 auto& router = ctx.registry.get().getHashRouter();
77
78 auto const id = ctx.tx.getTransactionID();
79 auto const flags = router.getFlags(id);
80
81 // If we haven't checked the condition, check it
82 // now. Whether it passes or not isn't important
83 // in preflight.
84 if (!any(flags & (SF_CF_INVALID | SF_CF_VALID)))
85 {
86 if (checkCondition(*fb, *cb))
87 {
88 router.setFlags(id, SF_CF_VALID);
89 }
90 else
91 {
92 router.setFlags(id, SF_CF_INVALID);
93 }
94 }
95 }
96
97 if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
98 return err;
99
100 return tesSUCCESS;
101}
102
105{
106 XRPAmount extraFee{0};
107
108 if (auto const fb = tx[~sfFulfillment])
109 {
110 extraFee += view.fees().base * (32 + (fb->size() / 16));
111 }
112
113 return Transactor::calculateBaseFee(view, tx) + extraFee;
114}
115
116template <ValidIssueType T>
117static TER
119 PreclaimContext const& ctx,
120 AccountID const& dest,
121 STAmount const& amount);
122
123template <>
126 PreclaimContext const& ctx,
127 AccountID const& dest,
128 STAmount const& amount)
129{
130 AccountID const issuer = amount.getIssuer();
131 // If the issuer is the same as the account, return tesSUCCESS
132 if (issuer == dest)
133 return tesSUCCESS;
134
135 // If the issuer has requireAuth set, check if the destination is authorized
136 if (auto const ter = requireAuth(ctx.view, amount.issue(), dest); !isTesSuccess(ter))
137 return ter;
138
139 // If the issuer has deep frozen the destination, return tecFROZEN
140 if (isDeepFrozen(ctx.view, dest, amount.getCurrency(), amount.getIssuer()))
141 return tecFROZEN;
142
143 return tesSUCCESS;
144}
145
146template <>
149 PreclaimContext const& ctx,
150 AccountID const& dest,
151 STAmount const& amount)
152{
153 AccountID const issuer = amount.getIssuer();
154 // If the issuer is the same as the dest, return tesSUCCESS
155 if (issuer == dest)
156 return tesSUCCESS;
157
158 // If the mpt does not exist, return tecOBJECT_NOT_FOUND
159 auto const issuanceKey = keylet::mptIssuance(amount.get<MPTIssue>().getMptID());
160 auto const sleIssuance = ctx.view.read(issuanceKey);
161 if (!sleIssuance)
162 return tecOBJECT_NOT_FOUND;
163
164 // If the issuer has requireAuth set, check if the destination is
165 // authorized
166 auto const& mptIssue = amount.get<MPTIssue>();
167 if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
168 !isTesSuccess(ter))
169 return ter;
170
171 // If the issuer has frozen the destination, return tecLOCKED
172 if (isFrozen(ctx.view, dest, mptIssue))
173 return tecLOCKED;
174
175 return tesSUCCESS;
176}
177
178TER
180{
181 if (ctx.view.rules().enabled(featureCredentials))
182 {
183 if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
184 !isTesSuccess(err))
185 return err;
186 }
187
188 if (ctx.view.rules().enabled(featureTokenEscrow))
189 {
190 auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
191 auto const slep = ctx.view.read(k);
192 if (!slep)
193 return tecNO_TARGET;
194
195 AccountID const dest = (*slep)[sfDestination];
196 STAmount const amount = (*slep)[sfAmount];
197
198 if (!isXRP(amount))
199 {
200 if (auto const ret = std::visit(
201 [&]<typename T>(T const&) {
202 return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
203 },
204 amount.asset().value());
205 !isTesSuccess(ret))
206 return ret;
207 }
208 }
209 return tesSUCCESS;
210}
211
212TER
214{
215 auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
216 auto const slep = ctx_.view().peek(k);
217 if (!slep)
218 {
219 if (ctx_.view().rules().enabled(featureTokenEscrow))
220 return tecINTERNAL; // LCOV_EXCL_LINE
221
222 return tecNO_TARGET;
223 }
224
225 // If a cancel time is present, a finish operation should only succeed prior
226 // to that time.
227 auto const now = ctx_.view().header().parentCloseTime;
228
229 // Too soon: can't execute before the finish time
230 if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
231 return tecNO_PERMISSION;
232
233 // Too late: can't execute after the cancel time
234 if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
235 return tecNO_PERMISSION;
236
237 // Check cryptocondition fulfillment
238 {
239 auto const id = ctx_.tx.getTransactionID();
240 auto flags = ctx_.registry.get().getHashRouter().getFlags(id);
241
242 auto const cb = ctx_.tx[~sfCondition];
243
244 // It's unlikely that the results of the check will
245 // expire from the hash router, but if it happens,
246 // simply re-run the check.
247 if (cb && !any(flags & (SF_CF_INVALID | SF_CF_VALID)))
248 {
249 // LCOV_EXCL_START
250 auto const fb = ctx_.tx[~sfFulfillment];
251
252 if (!fb)
253 return tecINTERNAL;
254
255 if (checkCondition(*fb, *cb))
256 {
257 flags = SF_CF_VALID;
258 }
259 else
260 {
261 flags = SF_CF_INVALID;
262 }
263
264 ctx_.registry.get().getHashRouter().setFlags(id, flags);
265 // LCOV_EXCL_STOP
266 }
267
268 // If the check failed, then simply return an error
269 // and don't look at anything else.
270 if (any(flags & SF_CF_INVALID))
272
273 // Check against condition in the ledger entry:
274 auto const cond = (*slep)[~sfCondition];
275
276 // If a condition wasn't specified during creation,
277 // one shouldn't be included now.
278 if (!cond && cb)
280
281 // If a condition was specified during creation of
282 // the suspended payment, the identical condition
283 // must be presented again. We don't check if the
284 // fulfillment matches the condition since we did
285 // that in preflight.
286 if (cond && (cond != cb))
288 }
289
290 // NOTE: Escrow payments cannot be used to fund accounts.
291 AccountID const destID = (*slep)[sfDestination];
292 auto const sled = ctx_.view().peek(keylet::account(destID));
293 if (!sled)
294 return tecNO_DST;
295
296 if (auto err = verifyDepositPreauth(ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
297 !isTesSuccess(err))
298 return err;
299
300 AccountID const account = (*slep)[sfAccount];
301
302 // Remove escrow from owner directory
303 {
304 auto const page = (*slep)[sfOwnerNode];
305 if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, k.key, true))
306 {
307 // LCOV_EXCL_START
308 JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
309 return tefBAD_LEDGER;
310 // LCOV_EXCL_STOP
311 }
312 }
313
314 // Remove escrow from recipient's owner directory, if present.
315 if (auto const optPage = (*slep)[~sfDestinationNode])
316 {
317 if (!ctx_.view().dirRemove(keylet::ownerDir(destID), *optPage, k.key, true))
318 {
319 // LCOV_EXCL_START
320 JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
321 return tefBAD_LEDGER;
322 // LCOV_EXCL_STOP
323 }
324 }
325
326 STAmount const amount = slep->getFieldAmount(sfAmount);
327 // Transfer amount to destination
328 if (isXRP(amount))
329 {
330 (*sled)[sfBalance] = (*sled)[sfBalance] + amount;
331 }
332 else
333 {
334 if (!ctx_.view().rules().enabled(featureTokenEscrow))
335 return temDISABLED; // LCOV_EXCL_LINE
336
337 Rate lockedRate = slep->isFieldPresent(sfTransferRate)
338 ? xrpl::Rate(slep->getFieldU32(sfTransferRate))
339 : parityRate;
340 auto const issuer = amount.getIssuer();
341 bool const createAsset = destID == account_;
342 if (auto const ret = std::visit(
343 [&]<typename T>(T const&) {
344 return escrowUnlockApplyHelper<T>(
345 ctx_.view(),
346 lockedRate,
347 sled,
349 amount,
350 issuer,
351 account,
352 destID,
353 createAsset,
354 j_);
355 },
356 amount.asset().value());
357 !isTesSuccess(ret))
358 return ret;
359
360 // Remove escrow from issuers owner directory, if present.
361 if (auto const optPage = (*slep)[~sfIssuerNode]; optPage)
362 {
363 if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true))
364 {
365 // LCOV_EXCL_START
366 JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
367 return tefBAD_LEDGER;
368 // LCOV_EXCL_STOP
369 }
370 }
371 }
372
373 ctx_.view().update(sled);
374
375 // Adjust source owner count
376 auto const sle = ctx_.view().peek(keylet::account(account));
378 ctx_.view().update(sle);
379
380 // Remove escrow from ledger
381 ctx_.view().erase(slep);
382 return tesSUCCESS;
383}
384
385} // namespace xrpl
Stream fatal() const
Definition Journal.h:325
STTx const & tx
std::reference_wrapper< ServiceRegistry > registry
beast::Journal const journal
ApplyView & view()
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
static bool checkExtraFeatures(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
TER doApply() override
static NotTEC preflight(PreflightContext const &ctx)
static NotTEC preflightSigValidated(PreflightContext const &ctx)
constexpr MPTID const & getMptID() const
Definition MPTIssue.h:26
A view into a ledger.
Definition ReadView.h:31
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual LedgerHeader const & header() const =0
Returns information about the ledger.
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:120
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:456
uint256 getTransactionID() const
Definition STTx.h:200
An immutable linear range of bytes.
Definition Slice.h:26
AccountID const account_
Definition Transactor.h:116
beast::Journal const j_
Definition Transactor.h:114
ApplyView & view()
Definition Transactor.h:132
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
XRPAmount preFeeBalance_
Definition Transactor.h:117
ApplyContext & ctx_
Definition Transactor.h:112
NotTEC checkFields(STTx const &tx, beast::Journal j)
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
Keylet escrow(AccountID const &src, std::uint32_t seq) noexcept
An escrow entry.
Definition Indexes.cpp:351
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:336
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:474
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:5
constexpr HashRouterFlags SF_CF_VALID
bool isXRP(AccountID const &c)
Definition AccountID.h:70
static TER escrowFinishPreclaimHelper(PreclaimContext const &ctx, AccountID const &dest, STAmount const &amount)
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE const > const &sleDst, beast::Journal j)
@ tefBAD_LEDGER
Definition TER.h:150
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
TER escrowFinishPreclaimHelper< Issue >(PreclaimContext const &ctx, AccountID const &dest, STAmount const &amount)
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, int depth=0)
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
TER escrowFinishPreclaimHelper< MPTIssue >(PreclaimContext const &ctx, AccountID const &dest, STAmount const &amount)
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:523
HashRouterFlags
Definition HashRouter.h:14
static bool checkCondition(Slice f, Slice c)
@ temMALFORMED
Definition TER.h:67
@ temDISABLED
Definition TER.h:94
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
@ tecLOCKED
Definition TER.h:339
@ tecNO_TARGET
Definition TER.h:285
@ tecOBJECT_NOT_FOUND
Definition TER.h:307
@ tecINTERNAL
Definition TER.h:291
@ tecFROZEN
Definition TER.h:284
@ tecCRYPTOCONDITION_ERROR
Definition TER.h:293
@ tecNO_PERMISSION
Definition TER.h:286
@ tecNO_DST
Definition TER.h:271
constexpr HashRouterFlags SF_CF_INVALID
@ tesSUCCESS
Definition TER.h:225
Rate const parityRate
A transfer rate signifying a 1:1 exchange.
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, int depth=0)
Check if the account lacks required authorization for MPT.
XRPAmount base
Cost of a reference transaction in drops.
uint256 key
Definition Keylet.h:20
NetClock::time_point parentCloseTime
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:57
ReadView const & view
Definition Transactor.h:60
beast::Journal const j
Definition Transactor.h:65
State information when preflighting a tx.
Definition Transactor.h:14
beast::Journal const j
Definition Transactor.h:21
std::reference_wrapper< ServiceRegistry > registry
Definition Transactor.h:16
Represents a transfer rate.
Definition Rate.h:20
T visit(T... args)