|
7 | 7 | #include <test/jtx/mpt.h> |
8 | 8 | #include <test/jtx/pay.h> |
9 | 9 | #include <test/jtx/permissioned_domains.h> |
| 10 | +#include <test/jtx/sig.h> |
| 11 | +#include <test/jtx/sponsor.h> |
10 | 12 | #include <test/jtx/tags.h> |
11 | 13 | #include <test/jtx/token.h> |
12 | 14 | #include <test/jtx/trust.h> |
@@ -5250,6 +5252,139 @@ class Invariants_test : public beast::unit_test::Suite |
5250 | 5252 | return true; |
5251 | 5253 | }); |
5252 | 5254 | } |
| 5255 | + |
| 5256 | + { |
| 5257 | + // XRPNotCreated: the sfFeeAmount escrowed in a Sponsorship entry |
| 5258 | + // counts toward the XRP total. Creating a Sponsorship holding a |
| 5259 | + // FeeAmount without debiting any account manufactures XRP. |
| 5260 | + doInvariantCheck( |
| 5261 | + {{"XRP net change was positive: 100000000"}}, |
| 5262 | + [](Account const& a1, Account const& a2, ApplyContext& ac) { |
| 5263 | + auto const sleNew = |
| 5264 | + std::make_shared<SLE>(keylet::sponsorship(a1.id(), a2.id())); |
| 5265 | + sleNew->setAccountID(sfOwner, a1.id()); |
| 5266 | + sleNew->setAccountID(sfSponsee, a2.id()); |
| 5267 | + sleNew->setFieldAmount(sfFeeAmount, XRP(100)); |
| 5268 | + ac.view().insert(sleNew); |
| 5269 | + return true; |
| 5270 | + }); |
| 5271 | + |
| 5272 | + // ... and deleting a Sponsorship entry without refunding its |
| 5273 | + // escrowed FeeAmount destroys XRP beyond the transaction fee. |
| 5274 | + doInvariantCheck( |
| 5275 | + {{"XRP net change of -100000000 doesn't match fee 0"}}, |
| 5276 | + [](Account const& a1, Account const& a2, ApplyContext& ac) { |
| 5277 | + auto const sle = ac.view().peek(keylet::sponsorship(a2.id(), a1.id())); |
| 5278 | + if (!sle) |
| 5279 | + return false; |
| 5280 | + ac.view().erase(sle); |
| 5281 | + return true; |
| 5282 | + }, |
| 5283 | + XRPAmount{}, |
| 5284 | + STTx{ttACCOUNT_SET, [](STObject&) {}}, |
| 5285 | + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, |
| 5286 | + [](Account const& a1, Account const& a2, Env& env) { |
| 5287 | + // a2 sponsors a1's fees with a pre-funded XRP(100) |
| 5288 | + env(sponsor::set_fee(a2, 0, XRP(100)), sponsor::SponseeAcc(a1)); |
| 5289 | + env.close(); |
| 5290 | + return env.le(keylet::sponsorship(a2.id(), a1.id())) != nullptr; |
| 5291 | + }); |
| 5292 | + } |
| 5293 | + |
| 5294 | + { |
| 5295 | + // Deleting a sponsored object without adjusting the accounts' |
| 5296 | + // Sponsored/SponsoringOwnerCount fields must be caught (the |
| 5297 | + // isDelete path of SponsorshipOwnerCountsMatch::visitEntry: the |
| 5298 | + // sponsored-object delta is -1 while the account deltas are 0). |
| 5299 | + auto const expectMessage = |
| 5300 | + "SponsoredObjectOwnerCount does not equal SponsoredOwnerCount delta."; |
| 5301 | + uint256 checkID; |
| 5302 | + |
| 5303 | + doInvariantCheck( |
| 5304 | + {{expectMessage}}, |
| 5305 | + [&](Account const&, Account const&, ApplyContext& ac) { |
| 5306 | + auto const check = ac.view().peek(keylet::check(checkID)); |
| 5307 | + if (!check) |
| 5308 | + return false; |
| 5309 | + ac.view().erase(check); |
| 5310 | + return true; |
| 5311 | + }, |
| 5312 | + XRPAmount{}, |
| 5313 | + STTx{ttACCOUNT_SET, [](STObject&) {}}, |
| 5314 | + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, |
| 5315 | + [&checkID](Account const& a1, Account const& a2, Env& env) { |
| 5316 | + // a2 reserve-sponsors a1's check by co-signing its creation |
| 5317 | + checkID = keylet::check(a1.id(), env.seq(a1)).key; |
| 5318 | + env(check::create(a1, a2, XRP(1)), |
| 5319 | + sponsor::As(a2, spfSponsorReserve), |
| 5320 | + Sig(sfSponsorSignature, a2), |
| 5321 | + Fee(XRP(1))); |
| 5322 | + env.close(); |
| 5323 | + auto const sle = env.le(keylet::check(checkID)); |
| 5324 | + return sle && sle->isFieldPresent(sfSponsor); |
| 5325 | + }); |
| 5326 | + } |
| 5327 | + |
| 5328 | + { |
| 5329 | + // Trust lines use sfLowSponsor/sfHighSponsor instead of sfSponsor |
| 5330 | + // and can be reserve-sponsored independently on each side. |
| 5331 | + auto const expectMessage = |
| 5332 | + "SponsoredObjectOwnerCount does not equal SponsoredOwnerCount delta."; |
| 5333 | + |
| 5334 | + auto const preclose = [](Account const& a1, Account const& a2, Env& env) { |
| 5335 | + env(trust(a1, a2["USD"](100))); |
| 5336 | + return true; |
| 5337 | + }; |
| 5338 | + |
| 5339 | + // Marking one side of an existing trust line as sponsored without |
| 5340 | + // touching any account counts (object delta 1, account deltas 0). |
| 5341 | + doInvariantCheck( |
| 5342 | + {{expectMessage}}, |
| 5343 | + [](Account const& a1, Account const& a2, ApplyContext& ac) { |
| 5344 | + auto const line = ac.view().peek(keylet::trustLine(a1, a2, a2["USD"].currency)); |
| 5345 | + if (!line) |
| 5346 | + return false; |
| 5347 | + line->setAccountID(sfLowSponsor, a2.id()); |
| 5348 | + ac.view().update(line); |
| 5349 | + return true; |
| 5350 | + }, |
| 5351 | + XRPAmount{}, |
| 5352 | + STTx{ttACCOUNT_SET, [](STObject&) {}}, |
| 5353 | + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, |
| 5354 | + preclose); |
| 5355 | + |
| 5356 | + // A trust line sponsored on both sides contributes two sponsored |
| 5357 | + // owner counts, so bumping the account fields by only one each is |
| 5358 | + // caught (object delta 2, account deltas 1). |
| 5359 | + doInvariantCheck( |
| 5360 | + {{expectMessage}}, |
| 5361 | + [](Account const& a1, Account const& a2, ApplyContext& ac) { |
| 5362 | + auto const line = ac.view().peek(keylet::trustLine(a1, a2, a2["USD"].currency)); |
| 5363 | + if (!line) |
| 5364 | + return false; |
| 5365 | + line->setAccountID(sfLowSponsor, a2.id()); |
| 5366 | + line->setAccountID(sfHighSponsor, a2.id()); |
| 5367 | + ac.view().update(line); |
| 5368 | + |
| 5369 | + // a1 owns the trust line, so its OwnerCount is already 1. |
| 5370 | + auto const sponsee = ac.view().peek(keylet::account(a1.id())); |
| 5371 | + if (!sponsee) |
| 5372 | + return false; |
| 5373 | + sponsee->setFieldU32(sfSponsoredOwnerCount, 1); |
| 5374 | + ac.view().update(sponsee); |
| 5375 | + |
| 5376 | + auto const sponsoring = ac.view().peek(keylet::account(a2.id())); |
| 5377 | + if (!sponsoring) |
| 5378 | + return false; |
| 5379 | + sponsoring->setFieldU32(sfSponsoringOwnerCount, 1); |
| 5380 | + ac.view().update(sponsoring); |
| 5381 | + return true; |
| 5382 | + }, |
| 5383 | + XRPAmount{}, |
| 5384 | + STTx{ttACCOUNT_SET, [](STObject&) {}}, |
| 5385 | + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, |
| 5386 | + preclose); |
| 5387 | + } |
5253 | 5388 | } |
5254 | 5389 |
|
5255 | 5390 | void |
|
0 commit comments