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/helpers/AccountRootHelpers.h>
13#include <xrpl/ledger/helpers/CredentialHelpers.h>
14#include <xrpl/ledger/helpers/EscrowHelpers.h>
15#include <xrpl/ledger/helpers/MPTokenHelpers.h>
16#include <xrpl/ledger/helpers/RippleStateHelpers.h>
17#include <xrpl/ledger/helpers/TokenHelpers.h>
18#include <xrpl/protocol/AccountID.h>
19#include <xrpl/protocol/Concepts.h>
20#include <xrpl/protocol/Feature.h>
21#include <xrpl/protocol/Indexes.h>
22#include <xrpl/protocol/Issue.h>
23#include <xrpl/protocol/MPTIssue.h>
24#include <xrpl/protocol/Rate.h>
25#include <xrpl/protocol/SField.h>
26#include <xrpl/protocol/STAmount.h>
27#include <xrpl/protocol/STLedgerEntry.h>
28#include <xrpl/protocol/STTx.h>
29#include <xrpl/protocol/SeqProxy.h>
30#include <xrpl/protocol/TER.h>
31#include <xrpl/protocol/XRPAmount.h>
32#include <xrpl/tx/Transactor.h>
33
34#include <system_error>
35#include <variant>
36
37namespace xrpl {
38
39// During an EscrowFinish, the transaction must specify both
40// a condition and a fulfillment. We track whether that
41// fulfillment matches and validates the condition.
44
45//------------------------------------------------------------------------------
46
47static bool
49{
50 using namespace xrpl::cryptoconditions;
51
53
54 auto condition = Condition::deserialize(c, ec);
55 if (!condition)
56 return false;
57
58 auto fulfillment = Fulfillment::deserialize(f, ec);
59 if (!fulfillment)
60 return false;
61
62 return validate(*fulfillment, *condition);
63}
64
65bool
67{
68 return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials);
69}
70
73{
74 auto const cb = ctx.tx[~sfCondition];
75 auto const fb = ctx.tx[~sfFulfillment];
76
77 // If you specify a condition, then you must also specify
78 // a fulfillment.
79 if (static_cast<bool>(cb) != static_cast<bool>(fb))
80 return temMALFORMED;
81
82 return tesSUCCESS;
83}
84
87{
88 auto const cb = ctx.tx[~sfCondition];
89 auto const fb = ctx.tx[~sfFulfillment];
90
91 if (cb && fb)
92 {
93 auto& router = ctx.registry.get().getHashRouter();
94
95 auto const id = ctx.tx.getTransactionID();
96 auto const flags = router.getFlags(id);
97
98 // If we haven't checked the condition, check it
99 // now. Whether it passes or not isn't important
100 // in preflight.
101 if (!any(flags & (kSfCfInvalid | kSfCfValid)))
102 {
103 if (checkCondition(*fb, *cb))
104 {
105 router.setFlags(id, kSfCfValid);
106 }
107 else
108 {
109 router.setFlags(id, kSfCfInvalid);
110 }
111 }
112 }
113
114 if (auto const err = credentials::checkFields(ctx.tx, ctx.rules, ctx.j); !isTesSuccess(err))
115 return err;
116
117 return tesSUCCESS;
118}
119
122{
123 XRPAmount extraFee{0};
124
125 if (auto const fb = tx[~sfFulfillment])
126 {
127 extraFee += view.fees().base * (32 + (fb->size() / 16));
128 }
129
130 return Transactor::calculateBaseFee(view, tx) + extraFee;
131}
132
133template <ValidIssueType T>
134static TER
136 PreclaimContext const& ctx,
137 AccountID const& dest,
138 STAmount const& amount);
139
140template <>
143 PreclaimContext const& ctx,
144 AccountID const& dest,
145 STAmount const& amount)
146{
147 AccountID const& issuer = amount.getIssuer();
148 // If the issuer is the same as the account, return tesSUCCESS
149 if (issuer == dest)
150 return tesSUCCESS;
151
152 // If the issuer has requireAuth set, check if the destination is authorized
153 if (auto const ter = requireAuth(ctx.view, amount.get<Issue>(), dest); !isTesSuccess(ter))
154 return ter;
155
156 // If the issuer has deep frozen the destination, return tecFROZEN
157 if (isDeepFrozen(ctx.view, dest, amount.get<Issue>().currency, amount.getIssuer()))
158 return tecFROZEN;
159
160 return tesSUCCESS;
161}
162
163template <>
166 PreclaimContext const& ctx,
167 AccountID const& dest,
168 STAmount const& amount)
169{
170 AccountID const& issuer = amount.getIssuer();
171 // If the issuer is the same as the dest, return tesSUCCESS
172 if (issuer == dest)
173 return tesSUCCESS;
174
175 // If the mpt does not exist, return tecOBJECT_NOT_FOUND
176 auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
177 auto const sleIssuance = ctx.view.read(issuanceKey);
178 if (!sleIssuance)
179 return tecOBJECT_NOT_FOUND;
180
181 // If the issuer has requireAuth set, check if the destination is
182 // authorized
183 auto const& mptIssue = amount.get<MPTIssue>();
184 if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
185 !isTesSuccess(ter))
186 return ter;
187
188 // If the issuer has frozen the destination, return tecLOCKED
189 if (isFrozen(ctx.view, dest, mptIssue))
190 return tecLOCKED;
191
192 return tesSUCCESS;
193}
194
195TER
197{
198 if (ctx.view.rules().enabled(featureCredentials))
199 {
200 if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
201 !isTesSuccess(err))
202 return err;
203 }
204
205 if (ctx.view.rules().enabled(featureTokenEscrow))
206 {
207 auto const seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]);
208 auto const k = keylet::escrow(ctx.tx[sfOwner], seqProxy);
209 auto const slep = ctx.view.read(k);
210 if (!slep)
211 return tecNO_TARGET;
212
213 AccountID const dest = (*slep)[sfDestination];
214 STAmount const amount = (*slep)[sfAmount];
215
216 if (!isXRP(amount))
217 {
218 if (auto const ret = std::visit(
219 [&]<typename T>(T const&) {
220 return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
221 },
222 amount.asset().value());
223 !isTesSuccess(ret))
224 return ret;
225 }
226 }
227 return tesSUCCESS;
228}
229
230TER
232{
233 auto const seqProxy = SeqProxy::rawSequence(ctx_.tx[sfOfferSequence]);
234 auto const k = keylet::escrow(ctx_.tx[sfOwner], seqProxy);
235 auto const slep = ctx_.view().peek(k);
236 if (!slep)
237 {
238 if (ctx_.view().rules().enabled(featureTokenEscrow))
239 return tecINTERNAL; // LCOV_EXCL_LINE
240
241 return tecNO_TARGET;
242 }
243
244 // If a cancel time is present, a finish operation should only succeed prior
245 // to that time.
246 auto const now = ctx_.view().header().parentCloseTime;
247
248 // Too soon: can't execute before the finish time
249 if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
250 return tecNO_PERMISSION;
251
252 // Too late: can't execute after the cancel time
253 if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
254 return tecNO_PERMISSION;
255
256 // Check cryptocondition fulfillment
257 {
258 auto const id = ctx_.tx.getTransactionID();
259 auto flags = ctx_.registry.get().getHashRouter().getFlags(id);
260
261 auto const cb = ctx_.tx[~sfCondition];
262
263 // It's unlikely that the results of the check will
264 // expire from the hash router, but if it happens,
265 // simply re-run the check.
266 if (cb && !any(flags & (kSfCfInvalid | kSfCfValid)))
267 {
268 // LCOV_EXCL_START
269 auto const fb = ctx_.tx[~sfFulfillment];
270
271 if (!fb)
272 return tecINTERNAL;
273
274 if (checkCondition(*fb, *cb))
275 {
276 flags = kSfCfValid;
277 }
278 else
279 {
280 flags = kSfCfInvalid;
281 }
282
283 ctx_.registry.get().getHashRouter().setFlags(id, flags);
284 // LCOV_EXCL_STOP
285 }
286
287 // If the check failed, then simply return an error
288 // and don't look at anything else.
289 if (any(flags & kSfCfInvalid))
291
292 // Check against condition in the ledger entry:
293 auto const cond = (*slep)[~sfCondition];
294
295 // If a condition wasn't specified during creation,
296 // one shouldn't be included now.
297 if (!cond && cb)
299
300 // If a condition was specified during creation of
301 // the suspended payment, the identical condition
302 // must be presented again. We don't check if the
303 // fulfillment matches the condition since we did
304 // that in preflight.
305 if (cond && (cond != cb))
307 }
308
309 // NOTE: Escrow payments cannot be used to fund accounts.
310 AccountID const destID = (*slep)[sfDestination];
311 auto const sled = ctx_.view().peek(keylet::account(destID));
312 if (!sled)
313 return tecNO_DST;
314
315 if (auto err =
316 verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, destID, sled, ctx_.journal);
317 !isTesSuccess(err))
318 return err;
319
320 AccountID const account = (*slep)[sfAccount];
321
322 // Remove escrow from owner directory
323 {
324 auto const page = (*slep)[sfOwnerNode];
325 if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, k.key, true))
326 {
327 // LCOV_EXCL_START
328 JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
329 return tefBAD_LEDGER;
330 // LCOV_EXCL_STOP
331 }
332 }
333
334 // Remove escrow from recipient's owner directory, if present.
335 if (auto const optPage = (*slep)[~sfDestinationNode])
336 {
337 if (!ctx_.view().dirRemove(keylet::ownerDir(destID), *optPage, k.key, true))
338 {
339 // LCOV_EXCL_START
340 JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
341 return tefBAD_LEDGER;
342 // LCOV_EXCL_STOP
343 }
344 }
345
346 // With the Sponsor amendment, release the escrow reserve before delivery.
347 // Token delivery can auto-create a destination holding, and the same
348 // sponsor (or the same account, for a self-escrow) may cover both the
349 // escrow being removed and the holding being created. Without the
350 // amendment, keep the legacy order: releasing early changes the reserve
351 // arithmetic for self-escrows and would break consensus if not gated.
352 bool const sponsorEnabled = ctx_.view().rules().enabled(featureSponsor);
353 if (sponsorEnabled)
354 decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
355
356 STAmount const amount = slep->getFieldAmount(sfAmount);
357 // Transfer amount to destination
358 if (isXRP(amount))
359 {
360 (*sled)[sfBalance] = (*sled)[sfBalance] + amount;
361 }
362 else
363 {
364 if (!ctx_.view().rules().enabled(featureTokenEscrow))
365 return temDISABLED; // LCOV_EXCL_LINE
366
367 Rate lockedRate = slep->isFieldPresent(sfTransferRate)
368 ? xrpl::Rate(slep->getFieldU32(sfTransferRate))
369 : kParityRate;
370 auto const issuer = amount.getIssuer();
371 bool const createAsset = destID == accountID_;
372 if (auto const ret = std::visit(
373 [&]<typename T>(T const&) {
375 ctx_.getApplyViewContext(),
376 lockedRate,
377 sled,
379 amount,
380 issuer,
381 account,
382 destID,
383 createAsset,
384 j_);
385 },
386 amount.asset().value());
387 !isTesSuccess(ret))
388 return ret;
389
390 // Remove escrow from issuers owner directory, if present.
391 if (auto const optPage = (*slep)[~sfIssuerNode]; optPage)
392 {
393 if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true))
394 {
395 // LCOV_EXCL_START
396 JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
397 return tefBAD_LEDGER;
398 // LCOV_EXCL_STOP
399 }
400 }
401 }
402
403 ctx_.view().update(sled);
404
405 // Adjust source owner count (legacy position, pre-Sponsor)
406 if (!sponsorEnabled)
407 decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
408
409 // Remove escrow from ledger
410 ctx_.view().erase(slep);
411 return tesSUCCESS;
412}
413
414void
416{
417 // No transaction-specific invariants yet (future work).
418}
419
420bool
422 STTx const&,
423 TER,
424 XRPAmount,
425 ReadView const&,
426 beast::Journal const&)
427{
428 // No transaction-specific invariants yet (future work).
429 return true;
430}
431} // 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::const_ref before, SLE::const_ref 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:180
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 & const_ref
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
uint256 getTransactionID() const
Definition STTx.h:238
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:155
ApplyView & view()
Definition Transactor.h:175
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
AccountID const accountID_
Definition Transactor.h:157
XRPAmount preFeeBalance_
Definition Transactor.h:158
ApplyContext & ctx_
Definition Transactor.h:153
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 escrow(AccountID const &src, SeqProxy const &seq) noexcept
An escrow entry.
Definition Indexes.cpp:388
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:373
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:537
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
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 isXRP(AccountID const &c)
Definition AccountID.h:84
static TER escrowFinishPreclaimHelper(PreclaimContext const &ctx, AccountID const &dest, STAmount const &amount)
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)
@ tefBAD_LEDGER
Definition TER.h:162
constexpr HashRouterFlags kSfCfInvalid
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:607
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:572
Rate const kParityRate
A transfer rate signifying a 1:1 exchange.
HashRouterFlags
Definition HashRouter.h:20
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, std::uint8_t depth=0)
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:676
TERSubset< CanCvtToTER > TER
Definition TER.h:647
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:361
@ tecNO_TARGET
Definition TER.h:307
@ tecOBJECT_NOT_FOUND
Definition TER.h:329
@ tecINTERNAL
Definition TER.h:313
@ tecFROZEN
Definition TER.h:306
@ tecCRYPTOCONDITION_ERROR
Definition TER.h:315
@ tecNO_PERMISSION
Definition TER.h:308
@ tecNO_DST
Definition TER.h:293
@ tesSUCCESS
Definition TER.h:245
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, SLE::const_ref sleDst, beast::Journal j)
constexpr bool any(HashRouterFlags flags)
Definition HashRouter.h:71
uint256 key
Definition Keylet.h:21
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:83
ReadView const & view
Definition Transactor.h:86
beast::Journal const j
Definition Transactor.h:91
State information when preflighting a tx.
Definition Transactor.h:38
beast::Journal const j
Definition Transactor.h:45
std::reference_wrapper< ServiceRegistry > registry
Definition Transactor.h:40
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)