xrpld
Loading...
Searching...
No Matches
EscrowFinish.cpp
1#include <xrpl/tx/transactors/escrow/EscrowFinish.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Slice.h>
5#include <xrpl/basics/chrono.h>
6#include <xrpl/conditions/Condition.h>
7#include <xrpl/conditions/Fulfillment.h>
8#include <xrpl/core/HashRouter.h>
9#include <xrpl/ledger/ApplyView.h>
10#include <xrpl/ledger/ReadView.h>
11#include <xrpl/ledger/View.h>
12#include <xrpl/ledger/entries/EscrowEntry.h>
13#include <xrpl/ledger/helpers/AccountRootHelpers.h>
14#include <xrpl/ledger/helpers/CredentialHelpers.h>
15#include <xrpl/ledger/helpers/EscrowHelpers.h>
16#include <xrpl/ledger/helpers/MPTokenHelpers.h>
17#include <xrpl/ledger/helpers/RippleStateHelpers.h>
18#include <xrpl/ledger/helpers/TokenHelpers.h>
19#include <xrpl/protocol/AccountID.h>
20#include <xrpl/protocol/Concepts.h>
21#include <xrpl/protocol/Feature.h>
22#include <xrpl/protocol/Indexes.h>
23#include <xrpl/protocol/Issue.h>
24#include <xrpl/protocol/MPTIssue.h>
25#include <xrpl/protocol/Rate.h>
26#include <xrpl/protocol/SField.h>
27#include <xrpl/protocol/STAmount.h>
28#include <xrpl/protocol/STLedgerEntry.h>
29#include <xrpl/protocol/STTx.h>
30#include <xrpl/protocol/SeqProxy.h>
31#include <xrpl/protocol/TER.h>
32#include <xrpl/protocol/XRPAmount.h>
33#include <xrpl/tx/Transactor.h>
34
35#include <system_error>
36#include <variant>
37
38namespace xrpl {
39
40// During an EscrowFinish, the transaction must specify both
41// a condition and a fulfillment. We track whether that
42// fulfillment matches and validates the condition.
45
46//------------------------------------------------------------------------------
47
48static bool
50{
51 using namespace xrpl::cryptoconditions;
52
54
55 auto condition = Condition::deserialize(c, ec);
56 if (!condition)
57 return false;
58
59 auto fulfillment = Fulfillment::deserialize(f, ec);
60 if (!fulfillment)
61 return false;
62
63 return validate(*fulfillment, *condition);
64}
65
66bool
68{
69 if (ctx.tx.isFieldPresent(sfCredentialIDs) && !ctx.rules.enabled(featureCredentials))
70 return false;
71
72 if (ctx.tx.isFieldPresent(sfGas) && !ctx.rules.enabled(featureSmartEscrow))
73 return false;
74
75 return true;
76}
77
80{
81 auto const cb = ctx.tx[~sfCondition];
82 auto const fb = ctx.tx[~sfFulfillment];
83
84 // If you specify a condition, then you must also specify
85 // a fulfillment.
86 if (static_cast<bool>(cb) != static_cast<bool>(fb))
87 return temMALFORMED;
88
89 return tesSUCCESS;
90}
91
94{
95 auto const cb = ctx.tx[~sfCondition];
96 auto const fb = ctx.tx[~sfFulfillment];
97
98 if (cb && fb)
99 {
100 auto& router = ctx.registry.get().getHashRouter();
101
102 auto const id = ctx.tx.getTransactionID();
103 auto const flags = router.getFlags(id);
104
105 // If we haven't checked the condition, check it
106 // now. Whether it passes or not isn't important
107 // in preflight.
108 if (!any(flags & (kSfCfInvalid | kSfCfValid)))
109 {
110 if (checkCondition(*fb, *cb))
111 {
112 router.setFlags(id, kSfCfValid);
113 }
114 else
115 {
116 router.setFlags(id, kSfCfInvalid);
117 }
118 }
119 }
120
121 if (auto const err = credentials::checkFields(ctx.tx, ctx.rules, ctx.j); !isTesSuccess(err))
122 return err;
123
124 return tesSUCCESS;
125}
126
129{
130 XRPAmount extraFee{0};
131
132 if (auto const fb = tx[~sfFulfillment])
133 {
134 extraFee += view.fees().base * (32 + (fb->size() / 16));
135 }
136
137 return Transactor::calculateBaseFee(view, tx) + extraFee;
138}
139
140template <ValidIssueType T>
141static TER
143 PreclaimContext const& ctx,
144 AccountID const& dest,
145 STAmount const& amount);
146
147template <>
150 PreclaimContext const& ctx,
151 AccountID const& dest,
152 STAmount const& amount)
153{
154 AccountID const& issuer = amount.getIssuer();
155 // If the issuer is the same as the account, return tesSUCCESS
156 if (issuer == dest)
157 return tesSUCCESS;
158
159 // If the issuer has requireAuth set, check if the destination is authorized
160 if (auto const ter = requireAuth(ctx.view, amount.get<Issue>(), dest); !isTesSuccess(ter))
161 return ter;
162
163 // If the issuer has deep frozen the destination, return tecFROZEN
164 if (isDeepFrozen(ctx.view, dest, amount.get<Issue>().currency, amount.getIssuer()))
165 return tecFROZEN;
166
167 return tesSUCCESS;
168}
169
170template <>
173 PreclaimContext const& ctx,
174 AccountID const& dest,
175 STAmount const& amount)
176{
177 AccountID const& issuer = amount.getIssuer();
178 // If the issuer is the same as the dest, return tesSUCCESS
179 if (issuer == dest)
180 return tesSUCCESS;
181
182 // If the mpt does not exist, return tecOBJECT_NOT_FOUND
183 auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
184 auto const sleIssuance = ctx.view.read(issuanceKey);
185 if (!sleIssuance)
186 return tecOBJECT_NOT_FOUND;
187
188 // If the issuer has requireAuth set, check if the destination is
189 // authorized
190 auto const& mptIssue = amount.get<MPTIssue>();
191 if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
192 !isTesSuccess(ter))
193 return ter;
194
195 // If the issuer has frozen the destination, return tecLOCKED
196 if (isFrozen(ctx.view, dest, *sleIssuance))
197 return tecLOCKED;
198
199 return tesSUCCESS;
200}
201
202TER
204{
205 if (ctx.view.rules().enabled(featureCredentials))
206 {
207 if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
208 !isTesSuccess(err))
209 return err;
210 }
211
212 if (ctx.view.rules().enabled(featureTokenEscrow))
213 {
214 auto const seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]);
215 EscrowEntryR const escrow(ctx.tx[sfOwner], seqProxy, ctx.view);
216 if (!escrow)
217 return tecNO_TARGET;
218
219 AccountID const dest = (*escrow)[sfDestination];
220 STAmount const amount = (*escrow)[sfAmount];
221
222 if (!isXRP(amount))
223 {
224 if (auto const ret = std::visit(
225 [&]<typename T>(T const&) {
226 return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
227 },
228 amount.asset().value());
229 !isTesSuccess(ret))
230 return ret;
231 }
232 }
233 return tesSUCCESS;
234}
235
236TER
238{
239 auto const seqProxy = SeqProxy::rawSequence(ctx_.tx[sfOfferSequence]);
240 EscrowEntryW escrow(ctx_.tx[sfOwner], seqProxy, ctx_.view(), j_);
241 if (!escrow)
242 {
243 if (ctx_.view().rules().enabled(featureTokenEscrow))
244 return tecINTERNAL; // LCOV_EXCL_LINE
245
246 return tecNO_TARGET;
247 }
248
249 // If a cancel time is present, a finish operation should only succeed prior
250 // to that time.
251 auto const now = ctx_.view().header().parentCloseTime;
252
253 // Too soon: can't execute before the finish time
254 if ((*escrow)[~sfFinishAfter] && !after(now, (*escrow)[sfFinishAfter]))
255 return tecNO_PERMISSION;
256
257 // Too late: can't execute after the cancel time
258 if ((*escrow)[~sfCancelAfter] && after(now, (*escrow)[sfCancelAfter]))
259 return tecNO_PERMISSION;
260
261 // Check cryptocondition fulfillment
262 {
263 auto const id = ctx_.tx.getTransactionID();
264 auto flags = ctx_.registry.get().getHashRouter().getFlags(id);
265
266 auto const cb = ctx_.tx[~sfCondition];
267
268 // It's unlikely that the results of the check will
269 // expire from the hash router, but if it happens,
270 // simply re-run the check.
271 if (cb && !any(flags & (kSfCfInvalid | kSfCfValid)))
272 {
273 // LCOV_EXCL_START
274 auto const fb = ctx_.tx[~sfFulfillment];
275
276 if (!fb)
277 return tecINTERNAL;
278
279 if (checkCondition(*fb, *cb))
280 {
281 flags = kSfCfValid;
282 }
283 else
284 {
285 flags = kSfCfInvalid;
286 }
287
288 ctx_.registry.get().getHashRouter().setFlags(id, flags);
289 // LCOV_EXCL_STOP
290 }
291
292 // If the check failed, then simply return an error
293 // and don't look at anything else.
294 if (any(flags & kSfCfInvalid))
296
297 // Check against condition in the ledger entry:
298 auto const cond = (*escrow)[~sfCondition];
299
300 // If a condition wasn't specified during creation,
301 // one shouldn't be included now.
302 if (!cond && cb)
304
305 // If a condition was specified during creation of
306 // the suspended payment, the identical condition
307 // must be presented again. We don't check if the
308 // fulfillment matches the condition since we did
309 // that in preflight.
310 if (cond && (cond != cb))
312 }
313
314 // NOTE: Escrow payments cannot be used to fund accounts.
315 AccountID const destID = (*escrow)[sfDestination];
316 auto const sled = ctx_.view().peek(keylet::account(destID));
317 if (!sled)
318 return tecNO_DST;
319
320 if (auto err =
321 verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, destID, sled, ctx_.journal);
322 !isTesSuccess(err))
323 return err;
324
325 AccountID const account = (*escrow)[sfAccount];
326
327 // Remove escrow from owner directory
328 {
329 auto const page = (*escrow)[sfOwnerNode];
330 if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, escrow.key(), true))
331 {
332 // LCOV_EXCL_START
333 JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
334 return tefBAD_LEDGER;
335 // LCOV_EXCL_STOP
336 }
337 }
338
339 // Remove escrow from recipient's owner directory, if present.
340 if (auto const optPage = (*escrow)[~sfDestinationNode])
341 {
342 if (!ctx_.view().dirRemove(keylet::ownerDir(destID), *optPage, escrow.key(), true))
343 {
344 // LCOV_EXCL_START
345 JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
346 return tefBAD_LEDGER;
347 // LCOV_EXCL_STOP
348 }
349 }
350
351 // Delivery can auto-create the destination's holding; the removed escrow
352 // must not be counted against its reserve. The two share a reserve payer
353 // for a self-escrow, or when one sponsor covers both.
354 bool const recycleReserve =
355 ctx_.view().rules().enabled(featureSponsor) || ctx_.view().rules().enabled(fixCleanup3_4_0);
356 if (recycleReserve)
357 decreaseOwnerCountForObject(ctx_.view(), account, escrow.mutableRawSle(), 1, ctx_.journal);
358
359 STAmount const amount = escrow->getFieldAmount(sfAmount);
360 // Transfer amount to destination
361 if (isXRP(amount))
362 {
363 (*sled)[sfBalance] = (*sled)[sfBalance] + amount;
364 }
365 else
366 {
367 if (!ctx_.view().rules().enabled(featureTokenEscrow))
368 return temDISABLED; // LCOV_EXCL_LINE
369
370 Rate lockedRate = escrow->isFieldPresent(sfTransferRate)
371 ? xrpl::Rate(escrow->getFieldU32(sfTransferRate))
372 : kParityRate;
373 auto const issuer = amount.getIssuer();
374 bool const createAsset = destID == accountID_;
375 if (auto const ret = std::visit(
376 [&]<typename T>(T const&) {
378 ctx_.getApplyViewContext(),
379 lockedRate,
380 sled,
382 amount,
383 issuer,
384 account,
385 destID,
386 createAsset,
387 j_);
388 },
389 amount.asset().value());
390 !isTesSuccess(ret))
391 return ret;
392
393 // Remove escrow from issuers owner directory, if present.
394 if (auto const optPage = (*escrow)[~sfIssuerNode]; optPage)
395 {
396 if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, escrow.key(), true))
397 {
398 // LCOV_EXCL_START
399 JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
400 return tefBAD_LEDGER;
401 // LCOV_EXCL_STOP
402 }
403 }
404 }
405
406 ctx_.view().update(sled);
407
408 if (!recycleReserve)
409 decreaseOwnerCountForObject(ctx_.view(), account, escrow.mutableRawSle(), 1, ctx_.journal);
410
411 // Remove escrow from ledger
412 escrow.erase();
413 return tesSUCCESS;
414}
415
416void
418{
419 // No transaction-specific invariants yet (future work).
420}
421
422bool
424 STTx const&,
425 TER,
426 XRPAmount,
427 ReadView const&,
428 beast::Journal const&)
429{
430 // No transaction-specific invariants yet (future work).
431 return true;
432}
433} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
static bool checkExtraFeatures(PreflightContext const &ctx)
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
static TER preclaim(PreclaimContext const &ctx)
TER doApply() override
void visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override
Inspect a single ledger entry modified by this transaction.
static NotTEC preflight(PreflightContext const &ctx)
static NotTEC preflightSigValidated(PreflightContext const &ctx)
A currency issued by an account.
Definition Issue.h:18
Currency currency
Definition Issue.h:20
constexpr MPTID const & getMptID() const
Definition MPTIssue.h:43
A view into a ledger.
Definition ReadView.h:41
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer 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:182
constexpr TIss const & get() const
Asset const & asset() const
Definition STAmount.h:496
AccountID const & getIssuer() const
Definition STAmount.h:516
std::shared_ptr< STLedgerEntry const > const & ConstRef
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
UInt256 getTransactionID() const
Definition STTx.h:262
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition SeqProxy.h:62
An immutable linear range of bytes.
Definition Slice.h:28
beast::Journal const j_
Definition Transactor.h:164
ApplyView & view()
Definition Transactor.h:184
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
AccountID const accountID_
Definition Transactor.h:166
XRPAmount preFeeBalance_
Definition Transactor.h:167
ApplyContext & ctx_
Definition Transactor.h:162
static std::unique_ptr< Condition > deserialize(Slice s, std::error_code &ec)
Load a condition from its binary form.
NotTEC checkFields(STTx const &tx, Rules const &rules, beast::Journal j)
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
bool validate(Fulfillment const &f, Condition const &c, Slice m)
Verify if the given message satisfies the fulfillment.
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:403
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:567
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool isXRP(AccountID const &c)
Definition AccountID.h:84
static TER escrowFinishPreclaimHelper(PreclaimContext const &ctx, AccountID const &dest, STAmount const &amount)
@ tefBAD_LEDGER
Definition TER.h:165
TER escrowUnlockApplyHelper(ApplyViewContext ctx, Rate lockedRate, SLE::Ref sleDest, XRPAmount xrpBalance, STAmount const &amount, AccountID const &issuer, AccountID const &sender, AccountID const &receiver, bool createAsset, beast::Journal journal)
constexpr HashRouterFlags kSfCfInvalid
EscrowEntry< ReadView > EscrowEntryR
Definition EscrowEntry.h:34
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)
constexpr HashRouterFlags kSfCfValid
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:614
TER escrowFinishPreclaimHelper< MPTIssue >(PreclaimContext const &ctx, AccountID const &dest, STAmount const &amount)
void decreaseOwnerCountForObject(ApplyView &view, SLE::Ref accountSle, SLE::Ref objectSle, std::uint32_t count, beast::Journal j)
Decrease owner-count fields for an existing ledger object.
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:644
Rate const kParityRate
A transfer rate signifying a 1:1 exchange.
EscrowEntry< ApplyView > EscrowEntryW
Definition EscrowEntry.h:35
HashRouterFlags
Definition HashRouter.h:20
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, std::uint8_t depth=0)
Returns true if account cannot send or receive tokens of mptIssue because a freeze applies.
static bool checkCondition(Slice f, Slice c)
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
@ temMALFORMED
Definition TER.h:75
@ temDISABLED
Definition TER.h:102
bool isTesSuccess(TER x) noexcept
Definition TER.h:683
TERSubset< CanCvtToTER > TER
Definition TER.h:654
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
@ tecLOCKED
Definition TER.h:366
@ tecNO_TARGET
Definition TER.h:312
@ tecOBJECT_NOT_FOUND
Definition TER.h:334
@ tecINTERNAL
Definition TER.h:318
@ tecFROZEN
Definition TER.h:311
@ tecCRYPTOCONDITION_ERROR
Definition TER.h:320
@ tecNO_PERMISSION
Definition TER.h:313
@ tecNO_DST
Definition TER.h:298
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, SLE::ConstRef sleDst, beast::Journal j)
@ tesSUCCESS
Definition TER.h:250
constexpr bool any(HashRouterFlags flags)
Definition HashRouter.h:74
UInt256 key
Definition Keylet.h:21
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:92
ReadView const & view
Definition Transactor.h:95
beast::Journal const j
Definition Transactor.h:100
State information when preflighting a tx.
Definition Transactor.h:39
beast::Journal const j
Definition Transactor.h:46
std::reference_wrapper< ServiceRegistry > registry
Definition Transactor.h:41
Represents a transfer rate.
Definition Rate.h:21
static std::unique_ptr< Fulfillment > deserialize(Slice s, std::error_code &ec)
Load a fulfillment from its binary form.
T visit(T... args)