Compare commits

...
396 Commits
Author SHA1 Message Date
Mayukha Vadari 7a85377730 change error code (#7790) 2026-07-10 16:55:41 -04:00
Kassaking7 710af21ce9 fix: SponsorshipSet cannot create empty ltSPONSORSHIP objects (#7788) 2026-07-10 16:35:12 -04:00
Mayukha Vadari a5ac1d4944 more fixes (part 7) (#7787) 2026-07-10 14:11:52 -04:00
Mayukha Vadari 6f40198823 Merge branch 'develop' into xrplf/sponsor 2026-07-10 11:40:00 -04:00
Olek 8114636103 Add more asserts, and other small fixes (#7784) 2026-07-10 11:35:47 -04:00
yinyiqian1 3760b70a5a more cleanups (#7780) 2026-07-09 21:23:07 -04:00
Mayukha Vadari 8d379087e5 small fixes (part 6) (#7782) 2026-07-09 20:04:30 -04:00
Mayukha Vadari f2761d931b add optional ter parameter to checkReserve (#7778) 2026-07-09 18:21:43 -04:00
Mayukha Vadari 9abeb52051 more fixes (part 5) (#7779) 2026-07-09 18:02:15 -04:00
Peter Chen a065ab6c09 add adjustLoanBrokerOwnerCount func (#7777) 2026-07-09 15:00:29 -07:00
Mayukha Vadari 6a6b97b529 more fixes from review (part 4) (#7775) 2026-07-09 15:36:14 -04:00
Kassaking7 152130082d Merge pull request #7765 from Kassaking7/codecov-test
fix: sponsor test coverage
2026-07-09 14:35:55 -04:00
Mayukha Vadari 28b7fb7cc7 more fixes from review part 3 (#7771) 2026-07-09 13:31:59 -04:00
yinyiqian1 e0c9cf8b97 address comments (#7768) 2026-07-09 13:07:34 -04:00
Peter Chen c74d394e8f Sponsor code clean up (#7767) 2026-07-09 10:05:57 -07:00
yinyiqian1 d324b7a731 refactor: combine reassign/create in SponsorshipTransfer doApply (#7766) 2026-07-08 17:54:31 -04:00
Mayukha Vadari 03d333855f refactor reserve calculations (#7747) 2026-07-08 14:36:29 -04:00
Mayukha Vadari 1c23b2acd5 refactor: rename terNO_SPONSORSHIP to terNO_PERMISSION (#7764) 2026-07-08 14:23:44 -04:00
Mayukha Vadari 63209d4d77 feat: Mark Sponsor as Supported::Yes (#7556) 2026-07-08 12:51:30 -04:00
Mayukha Vadari d8616d486c assorted fixes from review part 2 (#7757) 2026-07-08 12:29:22 -04:00
Olek 78e4bb17c8 Disallow fee sponsorship on Batch inner txs (#7754) 2026-07-08 12:29:17 -04:00
Kassaking7 2cfbf728bc Merge pull request #7762 from Kassaking7/merge_develop 2026-07-08 12:27:06 -04:00
Mayukha Vadari ec77e15ac7 assorted fixes from review (#7752) 2026-07-08 12:12:38 -04:00
Zhiyuan Wang 5b6ff52519 Merge branch 'develop' into merge_develop 2026-07-08 11:26:48 -04:00
yinyiqian1 5be01f06ce refactor/fix SponsorshipTransfer doApply (#7742) 2026-07-08 11:16:21 -04:00
yinyiqian1 6d8e7f45c5 Revert "fix: merge develop to sponsor (#7753)" (#7759) 2026-07-08 11:15:04 -04:00
Kassaking7 a5cced5d39 fix: Sponsored token EscrowFinish falsely rejects net-zero recycling (#7678) 2026-07-07 18:06:26 -04:00
Kassaking7 47043763af fix: merge develop to sponsor (#7753) 2026-07-07 18:06:10 -04:00
Zhiyuan Wang ec0716b7ba Merge branch 'develop' into merge_develop 2026-07-07 15:58:15 -04:00
Peter Chen 9b930d5dea fix: Change correct call sites to decreaseOwnerCountForObject (#7748) 2026-07-07 12:18:29 -07:00
Olek f514427847 Fix sandbox hooks to support sponsor (#7677) 2026-07-07 15:17:25 -04:00
Mayukha Vadari 5283e9dba1 test: Add more tests for sponsor edge cases (#7746) 2026-07-07 12:53:17 -04:00
Mayukha Vadariandxrplf-ai-reviewer[bot] a2f1418aa8 feat: Add Credential support (#7741)
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-07-07 11:06:45 -04:00
Mayukha Vadari af58fdac3c test: AccountDelete works with sponsored objects (#7740) 2026-07-07 11:06:31 -04:00
Peter Chen 7868d6ed79 fix sponsor fee (#7738) 2026-07-06 14:09:53 -07:00
Mayukha Vadari 70c16e8e1e fix: Remove SponsorshipTransfer from VaultInvariant/PermissionedDomainInvariant (#7739) 2026-07-06 16:48:59 -04:00
Mayukha Vadari 5670cc1316 refactor: Minor cleanups in AccountObjects.cpp (#7737) 2026-07-06 16:17:31 -04:00
yinyiqian1 8a71f96f78 fix and refactor SponsorshipTransfer preclaim (#7720) 2026-07-06 12:37:49 -04:00
Olek c738ad14ed Fix trust <-> sponsor interaction (#7710) 2026-07-06 11:16:35 -04:00
Mayukha Vadari 60702858a1 refactor: Move SponsorHelpers code to SponsorHelpers.cpp (#7713) 2026-07-03 21:00:42 -04:00
Mayukha Vadari c7b10a3153 refactor: Rename checkInsufficientReserve to checkReserve (#7712) 2026-07-03 21:00:16 -04:00
Olek cd5ce8dc50 Payment tx fee payer ID (#7682) 2026-07-02 22:08:37 -04:00
Peter Chen 12cf66259c Fix legacy SignerList sponsorship reserve mismatch (#7703) 2026-07-02 14:08:15 -07:00
yinyiqian1 ed65de1489 fix SponsorshipTransfer preflight (#7683) 2026-07-02 15:54:04 -04:00
yinyiqian1 11e6d65b30 support sponsor delegation integration (#7665) 2026-07-02 15:18:30 -04:00
Mayukha Vadari cecea82835 refactor: Use ApplyViewContext everywhere instead of ApplyView+STTx (#7618) 2026-07-02 13:22:04 -04:00
Peter Chen 0240203060 fix STAmount (#7697) 2026-07-02 08:41:11 -07:00
Ayaz Salikhov ad5bc87d1f Merge pull request #7700 from mathbunnyru/merge_develop_in_sponsor
chore: Merge develop to sponsor
2026-07-02 12:31:44 +01:00
Ayaz Salikhov 6c902777ee Add missing include 2026-07-02 11:48:43 +01:00
Ayaz Salikhov bd42a05275 Merge branch 'develop' into xrplf/sponsor 2026-07-02 11:22:19 +01:00
Mayukha Vadari 1c9168b29f refactor: Adjustment struct (#7680) 2026-07-01 18:17:29 -04:00
Mayukha Vadari 76acc2c92b refactor: Move baseAccountReserve back to Fees.h (#7688) 2026-07-01 18:03:07 -04:00
Mayukha Vadari 61896fd53d Merge pull request #7694 from XRPLF/mvadari/sponsor/merge-01-01
merge from develop
2026-07-01 18:02:27 -04:00
Mayukha Vadari cbf44b8436 fix clang-tidy 2026-07-01 17:28:03 -04:00
Mayukha Vadari 909f7ff102 fix forreal 2026-07-01 17:15:29 -04:00
Mayukha Vadari b664847d59 one more fingers crossed 2026-07-01 17:10:08 -04:00
Mayukha Vadari 5c17e879f7 more fixes 2026-07-01 16:47:15 -04:00
Peter Chen b590672929 Rename adjustOwnerCount and adjustOwnerCountObj (#7676) 2026-07-01 13:18:39 -07:00
Peter Chen b4bac1db65 SponsorshipSet Cleanup (#7679) 2026-07-01 13:13:08 -07:00
Mayukha Vadari dd489172c3 fix renames 2026-07-01 16:06:08 -04:00
Mayukha Vadari da31106aa5 more fixes 2026-07-01 15:51:51 -04:00
Mayukha Vadari d9c683d822 fix build 2026-07-01 15:47:47 -04:00
Mayukha Vadari 5b714a79c0 Merge branch 'develop' of https://github.com/XRPLF/rippled into xrplf/sponsor 2026-07-01 15:36:15 -04:00
Mayukha Vadari 7817245314 refactor: Switch checkInsufficientReserve from ReadView to ApplyView (#7667) 2026-07-01 13:18:20 -04:00
Mayukha Vadari 730e526080 fix: Better handling of txs that send XRP + charge reserve (#7671) 2026-07-01 11:37:45 -04:00
Kassaking7 4e6f8f334f fix: add missing tests to cover all scenario for sponsor reserve (#7574) 2026-06-30 17:24:33 -04:00
Mayukha Vadari 140d5ec297 fix: crash in reset (#7675) 2026-06-30 17:20:07 -04:00
yinyiqian1 efeedcdc60 fix: Disallow sponsored tx to be queued (#7674) 2026-06-30 14:21:10 -04:00
Mayukha Vadari 09b5cd9879 refactor: Move SponsorshipTransfer code around, add some comments (#7662) 2026-06-30 14:06:20 -04:00
Peter Chen f9af579690 Sponsor edge cases (#7558) 2026-06-30 09:41:44 -07:00
Mayukha Vadari 5bb979b2e5 feat: Add checks to block reserve sponsorship on OOS txs (#7605) 2026-06-30 12:39:09 -04:00
Zhiyuan Wang 12cafc87db fix: some fixes on test logic, style and comments (#7668) 2026-06-29 18:59:19 -04:00
Olek e42c7c8551 Revert back to simple and inconsistent reserve calculations. (#7633) 2026-06-29 16:50:30 -04:00
Mayukha Vadari 35e1a6ef25 refactor: Move reserve checks from preclaim to doApply where relevant (#7607) 2026-06-29 16:04:38 -04:00
Zhiyuan Wang 5029c08f12 fix: bunch of fixes on RPC (#7557) 2026-06-29 14:08:47 -04:00
Mayukha Vadari 132ced7d51 fix: 2 free token exception is not applicable for sponsor (#7644) 2026-06-29 11:43:51 -04:00
yinyiqian1 03d4e30378 Cut ticket scope (#7621) 2026-06-25 18:21:54 -04:00
Peter Chen 5eff397170 Remove AMM and Offer reserve from Sponsor (#7617) 2026-06-25 12:32:42 -07:00
Olek 86484cfa39 Remove sponsorship from vault and loan (#7628) 2026-06-25 14:36:46 -04:00
Mayukha Vadari ac86cdada7 feat: remove XChainBridge from scope (#7615) 2026-06-25 13:25:09 -04:00
Peter Chen c6889666c9 Remove Credential/oracle/PD/DID from sponsor (#7620) 2026-06-25 09:34:04 -07:00
yinyiqian1 9a7d1a114c Cut NFT out of scope (#7616) 2026-06-25 12:30:04 -04:00
Mayukha Vadariandxrplf-ai-reviewer[bot] 6ecf413c64 refactor: Clean up spf flag usage (#7608)
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-06-24 10:25:15 -04:00
Mayukha Vadari 8c98b50de8 refactor: Simplify variable names (#7604) 2026-06-23 20:15:04 -04:00
Mayukha Vadari 2b727cd292 fix: Remove callback in getPageForToken (#7576) 2026-06-23 17:28:50 -04:00
Mayukha Vadari 05bb41056d refactor: rename STTx::getFeePayer to getInitiator (#7603) 2026-06-23 15:48:51 -04:00
yinyiqian1 51264617f3 Merge pull request #7599 from yinyiqian1/merge-develop-sponsor
Merge develop sponsor and apply permissions.macro
2026-06-23 12:42:17 -04:00
Mayukha Vadari 05d3614a19 nit: fix spacing (#7594) 2026-06-23 00:17:32 -04:00
yinyiqian1 494391b4ab replace sponsorshipSet checkPermission with macro 2026-06-22 20:58:57 -04:00
yinyiqian1 fba4ba6ebd Merge remote-tracking branch 'origin/develop' into merge-develop-sponsor 2026-06-22 20:00:11 -04:00
yinyiqian1 018e736a3b Fix sponsor reserve accounting edge cases (#7575) 2026-06-22 15:18:22 -04:00
Mayukha Vadari 4a3abc0407 fix: Assorted AI comments (#7569) 2026-06-22 15:16:14 -04:00
Mayukha Vadari 1eef7473d3 Revert "Sponsorship should be non-obligated for sponsee (#7552)" (#7577) 2026-06-22 14:42:53 -04:00
Mayukha Vadari c3b3bce35d feat: Update to keylet::sponsorship and sfRemainingOwnerCount (#7578) 2026-06-22 11:07:59 -04:00
Mayukha Vadari d1a77b453e Merge branch 'develop' of https://github.com/XRPLF/rippled into xrplf/sponsor 2026-06-17 17:50:49 -04:00
Mayukha Vadari c2ab83c960 fix: Fix UBSan issue (#7554) 2026-06-17 17:50:17 -04:00
yinyiqian1 aac6be218a fix new CI rule switch fallthrough (#7568) 2026-06-17 17:39:12 -04:00
yinyiqian1 14986cc043 Sponsorship should be non-obligated for sponsee (#7552) 2026-06-17 14:08:06 -04:00
Oleksandr 4fc781ef93 Merge remote-tracking branch 'ripp/develop' into xrplf/sponsor 2026-06-15 18:37:38 -04:00
Oleksandr e52113956e clang-tidy 2026-06-15 18:21:33 -04:00
Oleksandr f650d52ada Merge remote-tracking branch 'ripple/develop' into xrplf/sponsor 2026-06-15 12:54:37 -04:00
Oleksandr 64159eb040 Revert clang-tidy 2026-06-03 22:40:10 -04:00
Oleksandr 942a950e93 Codegen update 2026-06-03 19:48:20 -04:00
Oleksandr d70eca3d7d Merge remote-tracking branch 'ripple/develop' into xrplf/sponsor 2026-06-03 19:47:19 -04:00
Oleksandr 7e5ac6f8cb Merge remote-tracking branch 'ripple/develop' into xrplf/sponsor 2026-06-03 19:31:46 -04:00
Oleksandr 03fb6d1e0c Codegen update 2026-06-03 18:29:55 -04:00
Oleksandr 9ef1949309 Merge remote-tracking branch 'ripple/develop' into xrplf/sponsor 2026-05-28 15:19:29 -04:00
Oleksandr 595b612589 Merge fixes 2026-05-28 01:54:20 -04:00
Oleksandr d561f0d84d Merge remote-tracking branch 'tequ/sponsor' into spns5 2026-05-26 17:06:36 -04:00
Oleksandr 3859fcad73 clang-tidy fixes 2026-05-26 17:06:36 -04:00
Oleksandr 8d905ac21b Merge remote-tracking branch 'ripp/develop' into spns5 2026-05-26 17:06:36 -04:00
Oleksandr 0997b92de3 Merge fixes 2026-05-26 17:04:16 -04:00
Oleksandr 9e5b7281cf Merge remote-tracking branch 'ripple/develop' into spns5 2026-05-26 17:00:29 -04:00
Oleksandr 0bfdfa73ff Merge fixes 2026-05-26 15:50:54 -04:00
Oleksandr 39e819927f Merge remote-tracking branch 'ripple/develop' into spns5 2026-05-26 15:49:42 -04:00
Oleksandr 08357b196f sponsor AccountID -> sponsorSle 2026-05-26 15:42:03 -04:00
tequ bb4c443fe5 fix ReserveCount Inflation via Co-Signed + Pre-Funded Interaction
fix #6864
2026-05-26 15:39:50 -04:00
tequ a669099f21 fix 2026-05-26 15:39:50 -04:00
tequ 7624c18c76 fix: XChainAddAccountCreateAttestation redirects relayer sponsorship to door-owned claim objects 2026-05-26 15:39:50 -04:00
tequ ab7bbac8f9 Merge remote-tracking branch 'oleks-rip/dev_rename_merge' into sponsor 2026-05-26 15:39:49 -04:00
Oleksandr 960aaec91e merge fixes 2026-05-26 15:39:46 -04:00
Oleksandr f042cd8e25 Merge remote-tracking branch 'ripple/develop' into dev_rename_merge 2026-05-26 15:37:34 -04:00
tequ 9d4c4968ad fix tidy error 2026-05-26 14:47:54 -04:00
Oleksandr b76652bdf4 XRPL_ASSERT -> Throw 2026-05-26 14:47:54 -04:00
tequ 405b314842 fix clang-tidy error 2026-05-26 14:47:54 -04:00
tequ 77ebc2921e clang-format 2026-05-26 14:47:54 -04:00
tequ c72fd59539 clang-tidy 2026-05-26 14:47:54 -04:00
tequ 6f4365557d Merge branch 'develop' into sponsor 2026-05-26 14:47:54 -04:00
Oleksandr d792e03468 adjustOwnerCountObj, more checks, some fixes 2026-05-26 14:47:53 -04:00
Oleksandr 2b4ce0fc21 Merge fixes 2026-05-26 14:47:53 -04:00
Oleksandr eb88c0ef6b Fix clang-format, some refactoring 2026-05-26 14:46:41 -04:00
tequ 50d5e2d996 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-05-26 14:46:41 -04:00
tequ bca6d8e515 fix not to payback ReserveCount 2026-05-26 14:46:41 -04:00
tequ 86c90c8e5c clang-tidy 2026-05-26 14:46:41 -04:00
tequ 0c683bfd66 levelization 2026-05-26 14:46:41 -04:00
tequ 33be9abbef clang-format 2026-05-26 14:46:41 -04:00
tequ aa70d6051b Merge remote-tracking branch 'upstream/develop' into sponsor 2026-05-26 14:46:41 -04:00
tequ d057f07d0e fix: setSponsorFieldU32 silently clamps to 0 on underflow instead of failing 2026-05-26 14:40:07 -04:00
tequ 688e568e39 fix: getLedgerEntryOwner missing ltLOAN_BROKER and ltLOAN prevents object sponsorship 2026-05-26 14:40:07 -04:00
tequ 3ad93af34d fix: Sponsor's MaxFee cap is bypassed in reset() path, allowing sponsee to drain entire pre-funded FeeAmount in a single tec-failing transaction 2026-05-26 14:40:07 -04:00
Oleksandr c5941d6b14 Unify accountReserve, ownerCount, ownerReserve 2026-05-26 14:40:07 -04:00
Oleksandr ee8d9ca73e More universal adjustSponsorOwnerCountHlp 2026-05-26 14:40:07 -04:00
Oleksandr 462a0b2338 Read/const_ref 2026-05-26 14:40:06 -04:00
Oleksandr 9b4ab67864 Fix delegable test 2026-05-26 14:40:06 -04:00
Oleksandr 84bffad2bf merge_fix 2026-05-26 14:40:06 -04:00
tequ b153213e83 run pre-commit 2026-04-28 17:20:00 +09:00
tequ c12eff6646 apply tidy diff 2026-04-28 17:09:57 +09:00
tequ fbff1c065c levelization 2026-04-28 17:04:14 +09:00
tequ e8deaa12d8 pre-commit run 2026-04-28 16:55:07 +09:00
tequ 6ee071c5b0 fix: An incorrect available XRP balance check in AMM deposit allows reserve-locked funds to be used as adding liquidity 2026-04-28 15:57:49 +09:00
tequ 381094498d Merge remote-tracking branch 'upstream/develop' into sponsor 2026-04-28 09:49:52 +09:00
tequ c072b125a0 fix: No invariant verifying sfSponsor field on objects matches sponsoring/sponsored count deltas #6897 2026-04-21 18:59:36 +09:00
tequ b2b2babe1e fix: PermissionedDomain invariant skips credential validation for SponsorshipTransfer #6902 2026-04-21 18:28:09 +09:00
tequ bb27479686 fix: TrustSet free trust line check uses sponsor's ownerCount, skipping sponsor reserve validation #6901 2026-04-21 17:51:50 +09:00
tequ 278e25d8ad fix: Preclaim XRP reserve overestimate for sponsored checks #6899 2026-04-21 17:25:33 +09:00
tequ 773acf0af7 fix: SponsoringAccountCount has no overflow protection #6898 2026-04-21 17:20:22 +09:00
tequ 7639bd9061 fix: MPTokenIssuanceDestroy reads sponsor from erased SLE #6895 2026-04-21 16:27:33 +09:00
tequ 207a33d3da fix: SponsorshipSet Update FeeAmount Lacks Reserve Floor Check 2026-04-21 15:56:28 +09:00
tequ 52e7cdc24d fix: Sponsored account creation double-counts base reserve #6894 2026-04-21 13:48:07 +09:00
tequ dd82977e6c Merge remote-tracking branch 'upstream/develop' into sponsor 2026-04-13 18:08:30 +09:00
tequ 31bc25973a fix SponsorshipSet Update FeeAmount Lacks Reserve Floor Check 2026-04-13 15:56:29 +09:00
tequ 021eaa81bf fix Co-Signed Sponsoring Bypasses Sponsor Balance Check 2026-04-13 15:30:33 +09:00
tequ 84e84fbadf fix Sponsor able to provide sponsorship with just base reserve 2026-04-13 14:03:37 +09:00
tequ b6b317597f add test for insufficient balance to sponsor fee 2026-04-10 21:43:35 +09:00
tequ 525adf122f fix reset() Does Not makeFieldAbsent for Zero FeeAmount 2026-04-10 18:28:28 +09:00
tequ 16e4f24226 fix XRPL_ASSERT(false) to UNREACHABLE 2026-04-10 17:59:25 +09:00
tequ 3df1e668da fix Unchecked std::optional Dereference in SponsorshipSet Create Path 2026-04-10 17:55:40 +09:00
tequ 9264136b36 fix deleteAMMTrustLine Reads Sponsor Fields After They Are Erased 2026-04-10 17:41:33 +09:00
tequ 6bd2e911eb fix Wrong Account in Sponsorship Keylet Lookup Causes Reserve Count
Leakage
2026-04-10 17:07:36 +09:00
tequ 828b4753fe fix Wrong Account Passed to getLedgerEntryOwner in doApply 2026-04-10 15:57:15 +09:00
tequ d524670bcd fix SponsorshipEnd Bypass 2026-04-10 15:54:16 +09:00
tequ 25de0ce1b1 fix comment 2026-04-10 14:21:05 +09:00
tequ 4026af175b format 2026-04-10 14:13:55 +09:00
tequ a115d73c56 return tecHAS_OBLIGATIONS for any conditions when deleting account linked to ltSponsorship 2026-04-10 14:13:25 +09:00
tequ 71003699dc Add test for no SponsorFlag with valid sponsor 2026-04-10 13:45:14 +09:00
tequ 831f99862d clang-tidy 2026-04-10 05:51:22 +09:00
tequ 4d2945323d fix AccountObjects_test.cpp error 2026-04-10 04:21:11 +09:00
tequ 700a528a25 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-04-10 03:38:07 +09:00
tequ 07d0887efb fix path 2026-04-09 00:46:20 +09:00
tequ 3ea0067e6a clang-format 2026-04-08 12:03:12 +09:00
tequ 5a195d2585 fix path 2026-04-08 11:55:35 +09:00
tequ 3f4e88c300 Merge remote-tracking branch 'xrplf/develop' into sponsor 2026-04-08 11:47:16 +09:00
tequ 7c3ca98bd5 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-31 14:22:49 +09:00
tequ 2505fa86f7 fix 2026-03-30 17:52:36 +09:00
tequ d0d4f1d153 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-30 17:25:14 +09:00
tequ 41429dcc99 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-25 11:59:44 +09:00
tequ aacb8e4a68 return tem error when SponsorReserve flag is set in Batch OuterTxn 2026-03-23 13:31:36 +09:00
tequ 98257d3b96 Fix SponsorshipSet targeting Pseudo Accounts to return an error 2026-03-23 13:07:28 +09:00
tequ afcc3c6464 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-23 12:46:27 +09:00
tequ fd393685dd fix L20 2026-03-23 12:46:17 +09:00
tequ de64bc7855 fix L18 2026-03-23 12:34:33 +09:00
tequ 1adaeac8a7 fix L14 2026-03-23 12:14:44 +09:00
tequ 7ddda1b1e6 fix L5 2026-03-23 12:01:30 +09:00
tequ 5280a185d0 fix L4 2026-03-23 11:59:46 +09:00
tequ 64f699a974 fix M17 2026-03-23 11:14:41 +09:00
tequ 968493f43b fix M16 2026-03-23 11:10:45 +09:00
tequ 22bfc5e743 fix M15 2026-03-23 11:02:59 +09:00
tequ 2f0adfae3b fix M11 2026-03-23 10:55:42 +09:00
tequ 53bdfb2bc7 Add comment to clarify that the door account should not have a sponsor in the reserve check 2026-03-23 10:28:27 +09:00
tequ b695b4bf9b Add comments for PreFunded sponsor reserve checks on TrustSet 2026-03-23 10:27:18 +09:00
tequ 6d7ffcbb91 fix M7 2026-03-19 15:39:55 +09:00
tequ d1346fa3f6 fix M6 2026-03-19 15:14:14 +09:00
tequ d6803868a1 fix M5 2026-03-19 15:00:09 +09:00
tequ 9a6432dd66 fix M3 2026-03-19 14:50:09 +09:00
tequ f8a8c2301c fix M2 2026-03-19 14:33:55 +09:00
tequ e40e15a866 fix C2, C4 2026-03-19 14:01:11 +09:00
tequ 6555001cd9 make sponsor helpers inline 2026-03-19 11:50:55 +09:00
tequ f13945eff9 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-19 11:10:44 +09:00
tequ b32ff18c21 refactor getFeePayer() 2026-03-18 00:19:21 +09:00
tequ 8baec5634c pre-commit run --all-files 2026-03-18 00:03:31 +09:00
tequ 37c95ead69 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-18 00:02:57 +09:00
tequ 234d2e5414 Refactor ownerCount and calculateReserve to use SLE::const_ref 2026-03-17 21:38:25 +09:00
tequ 024ab07eb4 minor fixes 2026-03-17 21:35:08 +09:00
tequ d95b6e41e5 Refactor variable declarations in xrpLiquid 2026-03-17 21:15:41 +09:00
tequ 8d75f3bdc6 remove asfDisallowIncomingSponsor 2026-03-17 21:13:35 +09:00
tequ eb7e01de7e Remove unused tx parameter from removeEmptyHolding() 2026-03-17 21:04:40 +09:00
tequ c79814a037 remove unused Sponsor.h 2026-03-17 20:39:57 +09:00
tequ 15be71469d remove unnecessary reserveCount check 2026-03-17 20:35:50 +09:00
tequ c9666d7b45 refactor adjustOwnerCount() to use adjustSponsorOwnerCountHlp() 2026-03-17 20:28:01 +09:00
tequ 839ba17dae Refactor: sponsorship-related helpers 2026-03-17 20:11:49 +09:00
tequ 85e1f2b16c Rename [sponsor|sponsee]Acc,[sponsor|sponsee]Account to [sponsor|sponsee]AccountID for clarity 2026-03-17 18:32:30 +09:00
tequ 8a05e3d93b remove unnecessary #endif in Sponsor.h and sponsor.h 2026-03-17 18:14:48 +09:00
tequ 8c8ecffe1f remove tx parameter from adjustOwnerCount() 2026-03-17 18:14:24 +09:00
tequ b9f1c97518 format SetTrust.cpp 2026-03-17 17:43:35 +09:00
tequ d1eb22b403 fix transactions.macro format 2026-03-17 17:38:04 +09:00
tequ fb86735800 use pragma once 2026-03-17 17:26:13 +09:00
tequ 53f407e1bd Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-17 17:22:54 +09:00
tequ 99618877e4 clang-format 2026-02-23 23:55:25 +09:00
tequ 062ece321b Merge remote-tracking branch 'upstream/develop' into sponsor 2026-02-23 23:20:21 +09:00
tequ 1ad6dd1846 Add tests for deleting Sponsor Account with ltSponsorship 2026-02-23 11:10:46 +09:00
tequ 4a91351bcd Add sfSponsee to ttSponsorshipTransfer 2026-02-22 17:21:04 +09:00
tequ f86b255e73 Add SponsorshipEnd/Create/Reassign flags for SponsorshipTransfer 2026-02-22 02:41:01 +09:00
tequ 178bb8f7e9 Allow zero value for ReserveCount, FeeAmount, MaxFee 2026-02-22 00:01:56 +09:00
tequ 293394fbc6 audit 14 2026-02-18 17:22:23 +09:00
tequ dcfcb1f3fd audit 13 2026-02-18 16:33:32 +09:00
tequ 2fd8e1be3c audit 12 2026-02-18 15:55:10 +09:00
tequ fe5fab00ed audit 11 2026-02-18 15:18:17 +09:00
tequ 9cde57d284 audit 10 2026-02-18 15:09:12 +09:00
tequ 9859ba85a8 audit 9 2026-02-18 15:01:35 +09:00
tequ f6d79f74d6 audit 8 2026-02-18 14:53:21 +09:00
tequ bf4fa37937 audit 7 2026-02-18 14:48:51 +09:00
tequ 686f945909 audit 6 2026-02-18 14:46:03 +09:00
tequ c1e1be510f audit 5 2026-02-18 14:39:27 +09:00
tequ d1d613da27 audit 4 2026-02-18 14:19:40 +09:00
tequ 392a913631 audit 3 2026-02-18 14:09:11 +09:00
tequ 446681dc3d Merge remote-tracking branch 'upstream/develop' into sponsor 2026-02-18 13:39:57 +09:00
tequ e1aee43359 address review 2026-02-03 14:45:55 +09:00
tequ fe075470f8 address review 2026-02-02 18:57:04 +09:00
tequ 49c52695cb sfSponsorAccount -> sfSponsor/sfCounterpartySponsor 2026-02-02 12:51:35 +09:00
tequ 37f4ea94cb Add tests for Loan 2026-02-02 12:30:06 +09:00
tequ f8209087a3 Add tests for LoanBroker transactions 2026-02-01 20:38:37 +09:00
tequ d390bc7a87 clang-format 2026-01-31 01:33:36 +09:00
tequ 4e87de7303 add test for Sponsored trustline 2026-01-31 01:31:37 +09:00
tequ 8d24afe59d Merge remote-tracking branch 'upstream/develop' into sponsor 2026-01-30 23:29:56 +09:00
tequ 527d7bbede fix to return error if FeeAmount > Balance on SponsorshipSet 2026-01-30 23:24:50 +09:00
tequ 4a205eb9d8 add checks to accountReserve and fix sponsored owner count for xrpLiquid 2026-01-30 22:23:45 +09:00
tequ 0a410024a5 refactor SponsorshipSet flag checks 2026-01-30 22:22:09 +09:00
tequ 8f8fc03800 Add InvariantChecks for if OwnerCount < SponsoredOwnerCount 2026-01-30 21:21:38 +09:00
tequ 40cf5993c1 Add sanity check for sponsoringAccountCount = 0 2026-01-30 20:36:43 +09:00
tequ 7cdaa24e63 Fixed the minimum amount for creating a Sponsored Account. 2026-01-30 20:27:46 +09:00
tequ 175a2dfd28 chang sfFeeAmount to soeOPTIONAL 2026-01-30 20:03:24 +09:00
tequ 4cd8d0d178 fix simulate test 2026-01-30 19:51:07 +09:00
tequ 5155a94015 Add sponsor tests for Batch innerTxn 2026-01-30 17:20:15 +09:00
tequ c0de722cfa sfHigh/LowSponsorAccount -> High/LowSponsor 2026-01-30 12:32:48 +09:00
tequ d7ab1b48e1 Separate test suite 2026-01-30 11:43:45 +09:00
tequ 65768237cd Payback ReserveCount 2026-01-30 11:36:56 +09:00
tequ 5f385e0f9b update to use sfSponsorFlags 2026-01-30 10:36:48 +09:00
tequ ab1de456cc add comment 2026-01-29 13:52:19 +09:00
tequ 8be44c7fc4 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-01-29 11:22:42 +09:00
tequ 87718bdb3b remove unused HashPrefix 2026-01-29 10:58:06 +09:00
tequ 88e870b1c6 change sfSponsor to STAccount, Sponsor flags as global flags 2026-01-27 12:38:01 +09:00
tequ ce8049a17f fix parseSponsorship 2026-01-27 11:34:08 +09:00
tequ 990627fc9e Merge remote-tracking branch 'upstream/develop' into sponsor 2026-01-27 10:38:24 +09:00
tequ 1823d70b21 refactor Oracle Reserve calculation 2026-01-27 10:34:15 +09:00
tequ f333dd1b2e add signature existence check, consume ReserveCount if pre-funded sponsoring 2026-01-27 10:33:52 +09:00
tequ 204138fb0e Revert the use of calculateReserve in xrpLiquid 2026-01-15 18:48:35 +09:00
tequ 52ae31bb17 Merge commit 'c9458b72cab68d3cbbf533cc87d14309c2eb93b7' into sponsor 2026-01-15 16:34:24 +09:00
tequ c3e5bcfafd address review (DeleteAccount) 2026-01-15 15:53:29 +09:00
tequ 3568df43c4 add calculateReserve helper 2026-01-15 15:01:55 +09:00
tequ c321e1070d address reviews 2026-01-15 14:51:05 +09:00
tequ 9e65dba253 fix redundant co-signed sponsor check 2026-01-15 14:32:47 +09:00
tequ 5b91d815d1 address revew (std::optional + std::shared_ptr<SLE>) 2026-01-15 14:29:37 +09:00
tequ f885f02ede address spec changes (sfReserveCount, sfFeeAmount) 2026-01-14 19:28:10 +09:00
tequ 1bc5a1bb43 address spec changes (payment flags/ iou amount) 2026-01-14 12:49:49 +09:00
tequ ec05613472 fix typo 2026-01-09 16:45:12 +09:00
tequ f88e964dac allow sponsee on cspell 2026-01-09 16:40:15 +09:00
tequ ffb538242a Merge remote-tracking branch 'upstream/develop' into sponsor 2026-01-09 16:38:44 +09:00
tequ 1e8f6ce1b5 address reviews 2026-01-09 16:31:22 +09:00
tequ 82b146bf03 fix ripple -> xrpl 2026-01-07 17:16:50 +09:00
tequ 0791ca164a fix template exception 2026-01-07 16:42:18 +09:00
tequ dc325a93e7 Merge branch 'develop' into sponsor 2026-01-07 12:35:47 +09:00
tequ edddf3fe4b address review 2026-01-07 12:34:51 +09:00
tequ cf03c4cb8f change Sponsor related fields to soeDEFAULT 2026-01-07 12:31:01 +09:00
tequ 2813fea294 address review 2026-01-07 11:39:56 +09:00
tequ 163a2acbf5 add sfPreviousTxnID and sfPreviousTxnLgrSeq to ltSponsorship 2026-01-07 11:27:10 +09:00
tequ 198f3f83db remove unused insSponsorCoSigning args 2026-01-07 11:14:01 +09:00
tequ 0f36de8ef5 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-01-07 10:58:21 +09:00
tequ ea29fe5ede Merge remote-tracking branch 'upstream/develop' into sponsor 2025-12-11 10:31:48 +09:00
tequ f5e2195bbb address reviews 2025-12-11 10:31:30 +09:00
tequ e49d338cd8 address SponsorshipSet reviews 2025-11-26 17:48:35 +09:00
tequ d96998ce47 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-11-26 17:20:19 +09:00
tequ a9629ae325 address reviews 2025-11-21 17:44:05 +09:00
tequ 7f0a940a43 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-11-21 17:18:20 +09:00
tequ c38cb902ad Merge remote-tracking branch 'upstream/develop' into sponsor 2025-11-21 17:17:48 +09:00
tequ b5db59644a update Sponsored VaultCreate test 2025-11-17 18:29:22 +09:00
tequ 9d1b5eb373 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-11-17 18:19:30 +09:00
tequ af9d91bc72 add tests fro SponsorshipTransfer 2025-11-10 22:22:32 +09:00
tequ 3f9673510a fix comment for LCOV 2025-11-10 21:50:31 +09:00
tequ a49cb91c73 add tests 2025-11-10 12:11:42 +09:00
tequ fbf403aaa4 add tests for sponsor field 2025-11-10 00:55:39 +09:00
tequ e03c7a9c96 add sponsor transfer test 2025-11-10 00:40:15 +09:00
tequ cd62f7f4cf remove copyright 2025-11-06 21:08:51 +09:00
tequ 297083e27b fix: specify type for adjust variable in SetOracle transaction processing 2025-11-06 18:36:17 +09:00
tequ 13145f6db2 fix header name 2025-11-06 18:34:24 +09:00
tequ 3e6d6c6f80 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-11-06 18:08:13 +09:00
tequ b4188a887a address reviews 2025-11-06 17:53:07 +09:00
tequ 774917db22 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-10-31 11:24:47 +09:00
tequ 965ecd3f27 Add sponsored outer batch 2025-10-28 21:07:20 +09:00
tequ d0bcca6bf1 Fix the behavior of co-sign + pre-fund 2025-10-28 17:18:38 +09:00
tequ 4fe7e87b4f Merge remote-tracking branch 'upstream/develop' into sponsor 2025-10-17 19:21:24 +09:00
tequ 2337d340e5 Allow delegation for Sponsorship transactions 2025-10-17 18:56:46 +09:00
tequ 5d597e3b69 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-10-16 22:08:42 +09:00
tequ d8dc000488 add prefunded sponsor tests 2025-10-09 19:12:14 +09:00
tequ a65cf6e07d add tests for sponsor flags 2025-10-08 14:09:02 +09:00
tequ 7f922c4919 reserve co-signing 2025-10-08 02:50:08 +09:00
tequ 754f597078 Support sfSponsorSignature autofilling in Simulate RPC 2025-10-07 16:02:02 +09:00
tequ 32cc7825f2 Merge branch 'refactor-simulate-autofill' into sponsor 2025-10-07 15:30:48 +09:00
tequ a6b0efff87 typo 2025-10-07 11:19:31 +09:00
tequ 28c5cadbea fix transactions.macro format 2025-10-07 10:45:30 +09:00
tequ 9df0ad64d1 Merge branch 'sponsor-new-signing' into sponsor 2025-10-07 09:24:58 +09:00
tequ 1441abcf01 use sfSponsorSignature 2025-10-07 09:01:45 +09:00
Ed Hennis 8d32b0f856 Add jtx, STObject, and RPC support for sig object fields 2025-10-07 08:41:43 +09:00
tequ d59772657e refactor: signature autofilling for Simulate RPC 2025-10-04 14:37:06 +09:00
Ed Hennis bc71d9c138 Add support for extra transaction signature validation
- Restructures `STTx` signature checking code to be able to handle
  a `sigObject`, which may be the full transaction, or may be an object
  field containing a separate signature. Either way, the `sigObject` can
  be a single- or multi-sign signature.
- This is distinct from 550f90a75e (#5594), which changed the check in
  Transactor, which validates whether a given account is allowed to sign
  for the given transaction. This cryptographically checks the signature
  validity.
2025-10-04 13:22:56 +09:00
tequ 101b70f0e8 add assert when adding/removing sponsor field to LedgerEntry 2025-10-03 15:07:46 +09:00
tequ ee385e9d3b fix bad merge 2025-10-03 15:04:33 +09:00
tequ 4126d53978 Merge remote-tracking branch 'origin/develop' into sponsor 2025-10-03 09:39:47 +09:00
tequ 59c3d5bddc add insufficient reserve check for xchain, vault 2025-10-02 23:37:44 +09:00
tequ bc1ef1e839 add tests for XChainBridge 2025-10-02 18:29:12 +09:00
tequ 5b64ff0dd6 address 5594, 5592 changes 2025-09-30 18:30:38 +09:00
tequ 1ee250881b Merge remote-tracking branch 'upstream/develop' into sponsor 2025-09-30 12:06:09 +09:00
tequ 75aaeb5aae Merge remote-tracking branch 'upstream/develop' into sponsor 2025-09-26 18:26:40 +09:00
tequ b09666210d add InvariantChecks for pseudo-account 2025-09-26 18:25:49 +09:00
tequ dbbfc1354c test for Vault 2025-09-26 18:21:24 +09:00
tequ e82c300de7 test Sponsor Reserve checks for AMM 2025-09-26 10:15:00 +09:00
tequ d5a1314c47 test Sponsor Reserve checks for TrustSet 2025-09-24 19:05:22 +09:00
tequ 699297f01a test Sponsor Reserve checks for SignerListSet 2025-09-24 18:59:16 +09:00
tequ 71b81b743e test Sponsor Reserve checks for PayChan, PermissionedDomain, Oracle 2025-09-24 17:35:23 +09:00
tequ ec11763def test Sponsor Reserve checks for NFToken 2025-09-24 17:12:02 +09:00
tequ cc4b07dbd6 test Sponsor Reserve checks for DID, Escrow 2025-09-24 16:18:09 +09:00
tequ b78885d98c test Sponsor Reserve checks for DepositPreauth 2025-09-24 15:59:06 +09:00
tequ 0c93bd6f08 test Sponsor Reserve checks for DelegateSet 2025-09-24 15:57:49 +09:00
tequ 9694de92da test Sponsor Reserve checks for Credentials 2025-09-24 15:54:48 +09:00
tequ c4b798e8ed test Sponsor Reserve checks for TicketCreate 2025-09-24 15:41:58 +09:00
tequ 350130911a test Sponsor Reserve checks for OfferCreate 2025-09-24 15:34:55 +09:00
tequ ab4ac64c92 test Sponsor Reserve checks for Checks 2025-09-24 15:05:31 +09:00
tequ aeaf679deb Merge remote-tracking branch 'upstream/develop' into sponsor 2025-09-24 13:42:57 +09:00
tequ ea72198a1d Transfer Trustline Sponsorship 2025-09-24 13:42:05 +09:00
tequ 6eb453f07d Merge branch 'develop' into sponsor 2025-09-20 23:08:53 +09:00
tequ d4947386d7 Fully Support NFTokenMint/Burn 2025-09-20 17:03:20 +09:00
tequ fed56b2eeb address SponsorAccount/Sponsee field changes 2025-09-20 14:29:33 +09:00
tequ f7e1d4bbb9 add AccountTx tests for Sponsorship 2025-09-20 12:20:29 +09:00
tequ 613fe48d54 update account_objects for sponsorship 2025-09-20 11:44:47 +09:00
tequ 5dc63b64eb fix SponsorshipTransfer 2025-09-20 10:34:46 +09:00
tequ cc5e063438 add Credential sponsor test 2025-09-19 21:25:46 +09:00
tequ 453fd23512 add Sponsor Oracle 2025-09-19 21:12:34 +09:00
tequ 89af81745b add tests 2025-09-19 01:36:45 +09:00
tequ a43ae9a3ce add InvariantCheck for sponsor count 2025-09-18 18:49:53 +09:00
tequ 2afe5707ab High/Low SponsorAccount 2025-09-18 16:32:50 +09:00
tequ 759f843020 Sponsor permissions 2025-09-16 23:11:19 +09:00
tequ 5ac7bbf51b test SponsorshipRequire flags 2025-09-15 22:48:40 +09:00
tequ 2a27280f1a fix test error 2025-09-15 22:07:43 +09:00
tequ 6c0732ccbe test checks 2025-09-15 20:29:58 +09:00
tequ 20bce64946 test AccountDelete for sponsorship 2025-09-15 19:09:57 +09:00
tequ 110b222579 add MaxFee test 2025-09-15 18:52:42 +09:00
tequ 1cdf7bb745 feePayer 2025-09-15 16:25:16 +09:00
tequ 4abd94e7ac MaxFee 2025-09-15 11:04:47 +09:00
tequ 6aa0331ffe sfAccount to sfOwner 2025-09-13 22:40:19 +09:00
tequ 4eea76ca92 Create Sponsored Account / AccountDelete with ltSponsorship, Sponsored Account 2025-09-13 22:34:54 +09:00
tequ 8e895a3e7d fullly rename 2025-09-13 09:49:30 +09:00
tequ e589b71ee0 v2. SponsorSet 2025-09-13 09:36:38 +09:00
tequ 02d8f9fbef Sponsor signing 2025-09-08 00:20:14 +09:00
tequ 9ff71aa109 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-09-06 23:11:10 +09:00
tequ 2071b39d6a use helper 2025-08-01 18:33:19 +09:00
tequ 5e65813a52 add reserve checks for SponsorTransfer 2025-08-01 18:21:44 +09:00
tequ abd8620c97 Add SponsorTransfer 2025-08-01 13:23:41 +09:00
tequ 6e01f233e4 fix fees().accountReserve(0) to fees().reserve 2025-08-01 01:59:08 +09:00
tequ 0bddc95444 clang-format 2025-08-01 01:19:37 +09:00
tequ 67bdd099c8 check reserve 2025-08-01 00:44:11 +09:00
tequ f0addd81a1 fix addEmptyHolding 2025-07-31 17:43:54 +09:00
tequ a798c60a08 test Disabled 2025-07-31 00:12:23 +09:00
tequ 198ed72fe3 fix OwnerCount 2025-07-31 00:12:09 +09:00
tequ d40f8cc232 SponsorReserve,SponsorAccount
- not fully tested
- need to modify: Reserve check where using sfOwnerCount
2025-07-30 23:39:14 +09:00
tequ 5b9c767b07 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-07-29 22:58:03 +09:00
Mayukha Vadari 43c16f35f7 PoC for sponsored reserve 2025-06-17 11:35:23 +09:00
Mayukha Vadari cad305ac7e PoC for sponsored fees 2025-06-17 11:32:13 +09:00
170 changed files with 13151 additions and 660 deletions

No files matched your search

+3
View File
@@ -279,6 +279,8 @@ words:
- sles
- soci
- socidb
- sponsee
- sponsees
- SRPMS
- sslws
- statsd
@@ -327,6 +329,7 @@ words:
- unserviced
- unshareable
- unshares
- unsponsored
- unsquelch
- unsquelched
- unsquelching
+1 -1
View File
@@ -595,7 +595,7 @@ template <std::size_t Bits, typename Tag>
[[nodiscard]] constexpr bool
operator==(BaseUInt<Bits, Tag> const& lhs, BaseUInt<Bits, Tag> const& rhs)
{
return (lhs <=> rhs) == 0;
return (lhs <=> rhs) == 0; // NOLINT(modernize-use-nullptr)
}
//------------------------------------------------------------------------------
+20 -1
View File
@@ -3,6 +3,7 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/OwnerCounts.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Issue.h> // IWYU pragma: keep
@@ -11,6 +12,7 @@
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STVector256.h>
#include <cstdint>
@@ -288,7 +290,7 @@ public:
// Called when the owner count changes
// This is required to support PaymentSandbox
virtual void
adjustOwnerCountHook(AccountID const& account, std::uint32_t cur, std::uint32_t next)
adjustOwnerCountHook(AccountID const& account, OwnerCounts const& cur, OwnerCounts const& next)
{
}
@@ -411,6 +413,23 @@ public:
emptyDirDelete(Keylet const& directory);
};
/** Bundles the mutable ledger view and the transaction being applied.
Passed together to avoid threading two separate parameters through every
helper that needs both the view (for state reads/writes) and the
transaction (for field inspection and metadata).
Both members are non-owning references; the caller is responsible for
ensuring that the referenced objects outlive the ApplyViewContext.
TODO: replace with ApplyContext after it's untangled with xrpl/tx
*/
struct ApplyViewContext
{
ApplyView& view;
STTx const& tx;
};
namespace directory {
/** Helper functions for managing low-level directory operations.
These are not part of the ApplyView interface.
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STInteger.h> // IWYU pragma: keep
#include <xrpl/protocol/STLedgerEntry.h>
#include <cstdint>
#include <limits>
namespace xrpl {
struct OwnerCounts
{
std::uint32_t owner = 0;
std::uint32_t sponsored = 0;
std::uint32_t sponsoring = 0;
OwnerCounts() = default;
OwnerCounts(SLE::const_ref sle)
: owner(sle->at(sfOwnerCount))
, sponsored(sle->at(sfSponsoredOwnerCount))
, sponsoring(sle->at(sfSponsoringOwnerCount))
{
XRPL_ASSERT(
owner >= sponsored,
"xrpl::OwnerCounts : OwnerCount must be greater than or equal to "
"SponsoredOwnerCount");
XRPL_ASSERT(sle->getType() == ltACCOUNT_ROOT, "xrpl::OwnerCounts : sle is AccountRoot");
}
[[nodiscard]] std::uint32_t
count() const
{
std::int64_t const x = static_cast<std::int64_t>(owner) - sponsored + sponsoring;
if (x < 0)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::OwnerCounts::count : count less than zero");
return 0;
// LCOV_EXCL_STOP
}
if (x > std::numeric_limits<std::uint32_t>::max())
return std::numeric_limits<std::uint32_t>::max(); // LCOV_EXCL_LINE
return static_cast<std::uint32_t>(x);
}
auto
operator<=>(OwnerCounts const& o) const
{
if (auto cmp = count() <=> o.count(); cmp != 0) // NOLINT(modernize-use-nullptr)
return cmp;
if (auto cmp = owner <=> o.owner; cmp != 0) // NOLINT(modernize-use-nullptr)
return cmp;
if (auto cmp = sponsored <=> o.sponsored; cmp != 0) // NOLINT(modernize-use-nullptr)
return cmp;
return sponsoring <=> o.sponsoring;
}
bool
operator==(OwnerCounts const& o) const
{
return this == &o ||
(owner == o.owner && sponsored == o.sponsored && sponsoring == o.sponsoring);
}
};
} // namespace xrpl
+8 -6
View File
@@ -1,6 +1,7 @@
#pragma once
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/OwnerCounts.h>
#include <xrpl/ledger/RawView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/detail/ApplyViewBase.h>
@@ -107,12 +108,12 @@ public:
issuerSelfDebitMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance);
void
ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next);
ownerCount(AccountID const& id, OwnerCounts const& cur, OwnerCounts const& next);
// Get the adjusted owner count. Since DeferredCredits is meant to be used
// in payments, and payments only decrease owner counts, return the max
// remembered owner count.
[[nodiscard]] std::optional<std::uint32_t>
[[nodiscard]] std::optional<OwnerCounts>
ownerCount(AccountID const& id) const;
void
@@ -124,7 +125,7 @@ private:
std::map<KeyIOU, ValueIOU> creditsIOU_;
std::map<MPTID, IssuerValueMPT> creditsMPT_;
std::map<AccountID, std::uint32_t> ownerCounts_;
std::map<AccountID, OwnerCounts> ownerCounts_;
};
} // namespace detail
@@ -218,10 +219,11 @@ public:
override;
void
adjustOwnerCountHook(AccountID const& account, std::uint32_t cur, std::uint32_t next) override;
adjustOwnerCountHook(AccountID const& account, OwnerCounts const& cur, OwnerCounts const& next)
override;
[[nodiscard]] std::uint32_t
ownerCountHook(AccountID const& account, std::uint32_t count) const override;
[[nodiscard]] OwnerCounts
ownerCountHook(AccountID const& account, OwnerCounts const& count) const override;
/** Apply changes to base view.
+4 -2
View File
@@ -4,6 +4,7 @@
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/hash/uhash.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/OwnerCounts.h>
#include <xrpl/ledger/detail/ReadViewFwdRange.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Fees.h>
@@ -13,6 +14,7 @@
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
@@ -189,8 +191,8 @@ public:
// changes that accounts make during a payment. `ownerCountHook` adjusts the
// ownerCount so it returns the max value of the ownerCount so far.
// This is required to support PaymentSandbox.
[[nodiscard]] virtual std::uint32_t
ownerCountHook(AccountID const& account, std::uint32_t count) const
[[nodiscard]] virtual OwnerCounts
ownerCountHook(AccountID const& account, OwnerCounts const& count) const
{
return count;
}
+1 -2
View File
@@ -210,8 +210,7 @@ canWithdraw(ReadView const& view, STTx const& tx);
[[nodiscard]] TER
doWithdraw(
ApplyView& view,
STTx const& tx,
ApplyViewContext ctx,
AccountID const& senderAcct,
AccountID const& dstAcct,
AccountID const& sourceAcct,
+284 -11
View File
@@ -14,6 +14,7 @@
#include <cstdint>
#include <expected>
#include <optional>
#include <set>
#include <vector>
@@ -26,21 +27,293 @@ namespace xrpl {
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, AccountID const& issuer);
// Calculate liquid XRP balance for an account.
// This function may be used to calculate the amount of XRP that
// the holder is able to freely spend. It subtracts reserve requirements.
//
// ownerCountAdj adjusts the owner count in case the caller calculates
// before ledger entries are added or removed. Positive to add, negative
// to subtract.
//
// @param ownerCountAdj positive to add to count, negative to reduce count.
/** Calculate liquid XRP balance for an account.
*
* This function may be used to calculate the amount of XRP that
* the holder is able to freely spend. It subtracts reserve requirements.
*
* ownerCountAdj adjusts the owner count in case the caller calculates
* before ledger entries are added or removed. Positive to add, negative
* to subtract.
*
* @param view The ledger view to read from
* @param id The account ID to check
* @param ownerCountAdj Positive to add to count, negative to reduce count
* @param j Journal for logging
* @return The liquid XRP amount available to the account
*/
[[nodiscard]] XRPAmount
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j);
/** Adjust the owner count up or down. */
struct Adjustment
{
std::int32_t ownerCountDelta = 0;
std::int32_t accountCountDelta = 0;
};
/** Returns the account reserve, in drops.
*
* Actual owner count can be adjusted by delta in ownerCountAdj
* Actual reserve count can be adjusted by delta in accountCountAdj
* The reserve is calculated as:
* (ownerCount + "sponsoring object count" - "sponsored object count" + additionalOwnerCount) *
* increment + (1 if not sponsored account + sponsoringAccountCount) * "reserve base"
*
* @param view The ledger view to read from
* @param sle The ledger entry for the account
* @param j Journal for logging
* @param adj Adjustment to the owner/account count (default: 0/0). Positive to add, negative to
* subtract.
* @return The account reserve amount in drops
*/
[[nodiscard]] XRPAmount
accountReserve(ReadView const& view, SLE::const_ref sle, beast::Journal j, Adjustment adj = {});
/** Convenience overload that accepts AccountID instead of SLE.
*
* @param view The ledger view to read from
* @param id The account ID
* @param j Journal for logging
* @param adj Adjustment to the owner/account count (default: 0/0). Positive to add, negative to
* subtract.
* @return The account reserve amount in drops
*/
[[nodiscard]] inline XRPAmount
accountReserve(ReadView const& view, AccountID const& id, beast::Journal j, Adjustment adj = {})
{
return accountReserve(view, view.read(keylet::account(id)), j, adj);
}
/** Check if an account has sufficient reserve.
*
* @param view The ledger view to read from
* @param tx The transaction being processed
* @param accSle The account's ledger entry
* @param accBalance The account's balance
* @param sponsorSle The sponsor's ledger entry (if applicable)
* @param adj Adjustment to the owner/account count (default: 0/0). Positive to add, negative to
* subtract.
* @param j Journal for logging (default: null sink)
* @param insufReserveCode The transaction result code to return if the reserve is insufficient
* (default: tecINSUFFICIENT_RESERVE).
* @return Transaction result code
*/
[[nodiscard]] TER
checkReserve(
ApplyViewContext ctx,
SLE::const_ref accSle,
XRPAmount accBalance,
SLE::const_ref sponsorSle,
Adjustment adj,
beast::Journal j,
TER insufReserveCode = tecINSUFFICIENT_RESERVE);
/** Check if an account has sufficient reserve, deriving the sponsor internally.
*
* Equivalent to the overload above, but resolves the sponsor via
* getEffectiveTxReserveSponsor(ctx, accSle) instead of taking it explicitly. Use this
* in the common case where the sponsor is simply the transaction's reserve
* sponsor for accSle. Callers that must force the account's-own-reserve branch
* (passing a null sponsor) or supply a different sponsor should use the
* explicit overload above.
*
* @param ctx The apply-view context (view + tx)
* @param accSle The account's ledger entry
* @param accBalance The account's balance
* @param adj Reserve adjustments (owner/account count deltas)
* @param j Journal for logging (default: null sink)
* @return Transaction result code
*/
[[nodiscard]] TER
checkReserve(
ApplyViewContext ctx,
SLE::const_ref accSle,
XRPAmount accBalance,
Adjustment adj,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
/** Return number of the objects which reserve is covered by the account(sle) (so called "owner
* count"). Actual owner count can be adjusted by delta in ownerCountAdj.
*
* @param sle The account's ledger entry
* @param j Journal for logging
* @param ownerCountAdj Adjustment to the owner count (default: 0)
* @return The adjusted owner count
*/
std::uint32_t
ownerCount(SLE::const_ref sle, beast::Journal j, std::int32_t ownerCountAdj = 0);
/** Increase owner-count fields when the caller supplies the sponsor.
*
* This helper does not create a ledger object. It updates reserve accounting
* after the caller has created/updated an object.
* If sponsorSle is provided, this also adjusts the account's sponsored count
* and the sponsor's sponsoring count.
*
* @param view The apply view for making changes
* @param accountSle The account's ledger entry
* @param sponsorSle The sponsor's ledger entry (if applicable)
* @param count Amount to add to the owner count
* @param j Journal for logging
*/
void
adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j);
increaseOwnerCount(
ApplyView& view,
SLE::ref accountSle,
SLE::ref sponsorSle,
std::uint32_t count,
beast::Journal j);
/** Increase owner-count fields, deriving the tx reserve sponsor internally.
*
* Equivalent to the overload above, but resolves the sponsor via
* getEffectiveTxReserveSponsor(ctx, accountSle) instead of taking it explicitly. Use
* this when the sponsor is the transaction's reserve sponsor for accountSle
* (the common create path). Deletion paths, which derive the sponsor from an
* object's sfSponsor field, should keep using the explicit overload.
*
* @param ctx The apply-view context (view + tx)
* @param accountSle The account's ledger entry
* @param count Amount to add to the owner count
* @param j Journal for logging
*/
void
increaseOwnerCount(
ApplyViewContext ctx,
SLE::ref accountSle,
std::uint32_t count,
beast::Journal j);
/** Convenience overload that accepts AccountID instead of SLE references.
*
* @param view The apply view for making changes
* @param account The account ID
* @param sponsor The optional sponsor account ID
* @param count Amount to add to the owner count
* @param j Journal for logging
*/
inline void
increaseOwnerCount(
ApplyView& view,
AccountID const& account,
std::optional<AccountID> const& sponsor,
std::uint32_t count,
beast::Journal j)
{
increaseOwnerCount(
view,
view.peek(keylet::account(account)),
sponsor ? view.peek(keylet::account(*sponsor)) : SLE::pointer(),
count,
j);
}
/** Decrease owner-count fields when the caller supplies the sponsor.
*
* This helper does not delete a ledger object. It updates reserve accounting
* after the caller has removed an owner-counted reserve, or for special
* owner-count changes whose sponsor cannot be derived from an object's
* sfSponsor field.
*
* @param view The apply view for making changes
* @param accountSle The account's ledger entry
* @param sponsorSle The sponsor's ledger entry (if applicable)
* @param count Amount to remove from the owner count
* @param j Journal for logging
*/
void
decreaseOwnerCount(
ApplyView& view,
SLE::ref accountSle,
SLE::ref sponsorSle,
std::uint32_t count,
beast::Journal j);
/** Convenience overload that accepts AccountID instead of SLE references.
*
* @param view The apply view for making changes
* @param account The account ID
* @param sponsor The optional sponsor account ID
* @param count Amount to remove from the owner count
* @param j Journal for logging
*/
inline void
decreaseOwnerCount(
ApplyView& view,
AccountID const& account,
std::optional<AccountID> const& sponsor,
std::uint32_t count,
beast::Journal j)
{
decreaseOwnerCount(
view,
view.peek(keylet::account(account)),
sponsor ? view.peek(keylet::account(*sponsor)) : SLE::pointer(),
count,
j);
}
/** Decrease owner-count fields for an existing ledger object.
*
* This helper derives the reserve sponsor from objectSle's sfSponsor field,
* then updates the same owner-count fields as decreaseOwnerCount. Use this
* when removing an existing object whose reserve sponsor is stored on that
* object.
*
* @param view The apply view for making changes
* @param accountSle The account's ledger entry
* @param objectSle The object's ledger entry
* @param count Amount to remove from the owner count
* @param j Journal for logging
*/
void
decreaseOwnerCountForObject(
ApplyView& view,
SLE::ref accountSle,
SLE::ref objectSle,
std::uint32_t count,
beast::Journal j);
/** Convenience overload that accepts AccountID instead of account SLE reference.
*
* @param view The apply view for making changes
* @param account The account ID
* @param objectSle The object's ledger entry
* @param count Amount to remove from the owner count
* @param j Journal for logging
*/
inline void
decreaseOwnerCountForObject(
ApplyView& view,
AccountID const& account,
SLE::ref objectSle,
std::uint32_t count,
beast::Journal j)
{
SLE::ref accountSle = view.peek(keylet::account(account));
decreaseOwnerCountForObject(view, accountSle, objectSle, count, j);
}
/** Adjust a LoanBroker's owner count.
*
* A LoanBroker's sfOwnerCount tracks the number of outstanding loans on
* that broker; it is not a reserve-backed owner count and is distinct
* from the broker's pseudo-account's owner count. Loans can never carry a
* reserve sponsor (LoanSet rejects reserve sponsorship at preflight), so
* this never involves sponsor accounting and never invokes the
* ownerCountHook used for ACCOUNT_ROOT reserve tracking.
*
* @param view The apply view for making changes
* @param brokerSle The LoanBroker's ledger entry
* @param delta Amount to add (positive) or remove (negative) from the count
* @param j Journal for logging
*/
void
adjustLoanBrokerOwnerCount(
ApplyView& view,
SLE::ref brokerSle,
std::int32_t delta,
beast::Journal j);
/** Returns IOU issuer transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
+46 -31
View File
@@ -6,6 +6,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
@@ -22,17 +23,15 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
namespace xrpl {
template <ValidIssueType T>
TER
escrowUnlockApplyHelper(
ApplyView& view,
ApplyViewContext ctx,
Rate lockedRate,
SLE::ref sleDest,
STAmount const& xrpBalance,
XRPAmount xrpBalance,
STAmount const& amount,
AccountID const& issuer,
AccountID const& sender,
@@ -43,10 +42,10 @@ escrowUnlockApplyHelper(
template <>
inline TER
escrowUnlockApplyHelper<Issue>(
ApplyView& view,
ApplyViewContext ctx,
Rate lockedRate,
SLE::ref sleDest,
STAmount const& xrpBalance,
XRPAmount xrpBalance,
STAmount const& amount,
AccountID const& issuer,
AccountID const& sender,
@@ -66,16 +65,26 @@ escrowUnlockApplyHelper<Issue>(
if (receiverIssuer)
return tesSUCCESS;
if (!view.exists(trustLineKey) && createAsset)
if (!ctx.view.exists(trustLineKey) && createAsset)
{
// Can the account cover the trust line's reserve?
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
xrpBalance < view.fees().accountReserve(ownerCount + 1))
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx,
sleDest,
xrpBalance,
*sponsorSle,
{.ownerCountDelta = 1},
journal,
tecNO_LINE_INSUF_RESERVE);
!isTesSuccess(ret))
{
JLOG(journal.trace()) << "Trust line does not exist. "
"Insufficient reserve to create line.";
return tecNO_LINE_INSUF_RESERVE;
return ret;
}
Currency const currency = issue.currency;
@@ -83,7 +92,7 @@ escrowUnlockApplyHelper<Issue>(
initialBalance.get<Issue>().account = noAccount();
if (TER const ter = trustCreate(
view, // payment sandbox
ctx.view, // payment sandbox
recvLow, // is dest low?
issuer, // source
receiver, // destination
@@ -97,19 +106,20 @@ escrowUnlockApplyHelper<Issue>(
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
*sponsorSle, // sponsor
journal); // journal
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
view.update(sleDest);
ctx.view.update(sleDest);
}
if (!view.exists(trustLineKey) && !receiverIssuer)
if (!ctx.view.exists(trustLineKey) && !receiverIssuer)
return tecNO_LINE;
auto const xferRate = transferRate(view, amount);
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
@@ -137,7 +147,7 @@ escrowUnlockApplyHelper<Issue>(
// of the funds
if (!createAsset)
{
auto const sleRippleState = view.peek(trustLineKey);
auto const sleRippleState = ctx.view.peek(trustLineKey);
if (!sleRippleState)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -163,7 +173,7 @@ escrowUnlockApplyHelper<Issue>(
// if destination is not the issuer then transfer funds
if (!receiverIssuer)
{
auto const ter = directSendNoFee(view, issuer, receiver, finalAmt, true, journal);
auto const ter = directSendNoFee(ctx.view, issuer, receiver, finalAmt, true, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
}
@@ -173,10 +183,10 @@ escrowUnlockApplyHelper<Issue>(
template <>
inline TER
escrowUnlockApplyHelper<MPTIssue>(
ApplyView& view,
ApplyViewContext ctx,
Rate lockedRate,
SLE::ref sleDest,
STAmount const& xrpBalance,
XRPAmount xrpBalance,
STAmount const& amount,
AccountID const& issuer,
AccountID const& sender,
@@ -189,27 +199,32 @@ escrowUnlockApplyHelper<MPTIssue>(
auto const mptID = amount.get<MPTIssue>().getMptID();
auto const issuanceKey = keylet::mptokenIssuance(mptID);
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset && !receiverIssuer)
auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver);
if (!ctx.view.exists(mptKeylet) && createAsset && !receiverIssuer)
{
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
xrpBalance < view.fees().accountReserve(ownerCount + 1))
{
return tecINSUFFICIENT_RESERVE;
}
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ter = createMPToken(view, mptID, receiver, 0); !isTesSuccess(ter))
if (auto const ret = checkReserve(
ctx, sleDest, xrpBalance, *sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return ret;
if (auto const ter = createMPToken(ctx.view, mptID, receiver, *sponsorSle, 0);
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
// update owner count.
adjustOwnerCount(view, sleDest, 1, journal);
increaseOwnerCount(ctx.view, sleDest, *sponsorSle, 1, journal);
}
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && !receiverIssuer)
if (!ctx.view.exists(mptKeylet) && !receiverIssuer)
return tecNO_PERMISSION;
auto const xferRate = transferRate(view, amount);
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
@@ -232,11 +247,11 @@ escrowUnlockApplyHelper<MPTIssue>(
finalAmt = amount.value() - xferFee;
}
return unlockEscrowMPT(
view,
ctx.view,
sender,
receiver,
finalAmt,
view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
ctx.view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
journal);
}
+6 -4
View File
@@ -86,7 +86,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue);
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
ApplyViewContext ctx,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
@@ -117,7 +117,7 @@ requireAuth(
*/
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
ApplyViewContext ctx,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance,
@@ -203,7 +203,7 @@ canMPTTradeAndTransfer(
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
ApplyViewContext ctx,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
@@ -211,7 +211,7 @@ addEmptyHolding(
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
ApplyViewContext ctx,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal);
@@ -243,6 +243,7 @@ createMPToken(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
SLE::ref sponsorSle,
std::uint32_t const flags);
TER
@@ -250,6 +251,7 @@ checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
SLE::ref sponsorSle,
beast::Journal j);
//------------------------------------------------------------------------------
@@ -0,0 +1,31 @@
#pragma once
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h> // IWYU pragma: keep
#include <xrpl/protocol/STLedgerEntry.h>
#include <cstddef>
#include <cstdint>
namespace xrpl {
constexpr std::uint32_t kMinOracleReserveCount = 1;
constexpr std::uint32_t kMaxOracleReserveCount = 2;
constexpr std::size_t kOracleReserveCountThreshold = 5;
template <typename T>
requires requires(T const& t) { t.size(); }
inline std::uint32_t
calculateOracleReserve(T const& priceDataSeries)
{
return priceDataSeries.size() > kOracleReserveCountThreshold ? kMaxOracleReserveCount
: kMinOracleReserveCount;
}
inline std::uint32_t
calculateOracleReserve(SLE::const_ref oracleSle)
{
return calculateOracleReserve(oracleSle->getFieldArray(sfPriceDataSeries));
}
} // namespace xrpl
@@ -156,6 +156,7 @@ trustCreate(
// Issuer should be the account being set.
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
SLE::ref sponsorSle,
beast::Journal j);
[[nodiscard]] TER
@@ -178,6 +179,7 @@ issueIOU(
AccountID const& account,
STAmount const& amount,
Issue const& issue,
SLE::ref sponsorSle,
beast::Journal j);
[[nodiscard]] TER
@@ -235,7 +237,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
/// canAddHolding() in preflight with the same View and Asset
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
ApplyViewContext ctx,
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,
@@ -243,7 +245,7 @@ addEmptyHolding(
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
ApplyViewContext ctx,
AccountID const& accountID,
Issue const& issue,
beast::Journal journal);
@@ -0,0 +1,184 @@
#pragma once
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
#include <cstdint>
#include <expected>
#include <optional>
namespace xrpl {
/** Whether the given transaction type may use reserve sponsorship (v1).
*
* Reserve sponsorship is restricted to an explicit allow-list of transaction
* types; all others reject spfSponsorReserve at preflight.
*/
bool
isReserveSponsorAllowed(TxType txType);
/** Whether the transaction's fee is sponsored (sfSponsor present + spfSponsorFee set). */
inline bool
isFeeSponsored(STTx const& tx)
{
return tx.isFieldPresent(sfSponsor) && ((tx.getFieldU32(sfSponsorFlags) & spfSponsorFee) != 0u);
}
/** Whether the transaction's reserve is sponsored (sfSponsor present + spfSponsorReserve set). */
inline bool
isReserveSponsored(STTx const& tx)
{
return tx.isFieldPresent(sfSponsor) &&
((tx.getFieldU32(sfSponsorFlags) & spfSponsorReserve) != 0u);
}
/** Return the AccountID of the transaction's reserve sponsor, or nullopt if unsponsored. */
std::optional<AccountID>
getTxReserveSponsorID(STTx const& tx);
/** Return a mutable SLE for the transaction's reserve sponsor account.
*
* @param ctx The apply-view context (view + tx)
* @return The sponsor account SLE, a null pointer if the tx is not
* reserve-sponsored, or tecINTERNAL if the sponsor account cannot
* be loaded (an already-checked invariant).
*/
std::expected<SLE::pointer, TER>
getTxReserveSponsor(ApplyViewContext ctx);
/** Return a read-only SLE for the transaction's reserve sponsor account.
*
* @param view The ledger read view
* @param tx The transaction to inspect
* @return The sponsor account SLE, a null pointer if the tx is not
* reserve-sponsored, or tecINTERNAL if the sponsor account cannot
* be loaded (an already-checked invariant).
*/
std::expected<SLE::const_pointer, TER>
getTxReserveSponsor(ReadView const& view, STTx const& tx);
/** The transaction's reserve sponsor for the given account, if applicable.
*
* A reserve sponsor only covers the transaction submitter's own objects, so
* this returns the tx reserve sponsor SLE only when accountSle is the tx's own
* (non-pseudo) account; otherwise it returns a null sponsor pointer. This is
* the single source of truth for the "sponsor applies to tx.Account only" rule
* that the sponsor-deriving helper overloads in AccountRootHelpers rely on.
*
* @param ctx The apply-view context (view + tx)
* @param accountSle The account whose sponsor is being resolved
* @return The sponsor SLE (nullptr if unsponsored), or tecINTERNAL if the
* sponsor account cannot be loaded (an already-checked invariant)
*/
[[nodiscard]] std::expected<SLE::pointer, TER>
getEffectiveTxReserveSponsor(ApplyViewContext ctx, SLE::const_ref accountSle);
/** Return the AccountID stored in the given sponsor field of a ledger entry, or nullopt if absent.
*/
std::optional<AccountID>
getLedgerEntryReserveSponsorID(SLE::const_ref sle, SF_ACCOUNT const& field = sfSponsor);
/** Return a mutable SLE for the reserve sponsor recorded on a ledger entry.
*
* Reads the sponsor AccountID from @p field on @p sle and peeks the
* corresponding account root in @p view.
*
* @param view The mutable apply view
* @param sle The ledger entry whose sponsor field is inspected
* @param field The field that holds the sponsor AccountID (defaults to sfSponsor)
* @return The sponsor account SLE, or a null pointer if the entry is unsponsored.
*/
SLE::pointer
getLedgerEntryReserveSponsor(
ApplyView& view,
SLE::const_ref sle,
SF_ACCOUNT const& field = sfSponsor);
/** Stamp a reserve sponsor onto a ledger entry using an explicit sponsor SLE.
*
* Sets @p field on @p sle to the AccountID from @p sponsorSle. A no-op when
* @p sponsorSle is null (unsponsored). For RippleState entries the field must
* be sfHighSponsor or sfLowSponsor; for all other entry types it must be
* sfSponsor.
*
* @param sle The ledger entry to stamp
* @param sponsorSle The sponsor's account root SLE (null → no-op)
* @param field The sponsor field to set (defaults to sfSponsor)
*/
void
addSponsorToLedgerEntry(
SLE::ref sle,
SLE::const_ref sponsorSle,
SF_ACCOUNT const& field = sfSponsor);
/** Stamp the transaction's reserve sponsor onto a newly-created ledger entry.
*
* Equivalent to the overload above, but resolves the sponsor via
* getTxReserveSponsor(ctx) instead of taking it explicitly. A no-op when the
* transaction is not reserve-sponsored. The entry is assumed to be owned by
* the transaction submitter, which is the only account a tx reserve sponsor
* can cover.
*/
void
addSponsorToLedgerEntry(ApplyViewContext ctx, SLE::ref sle, SF_ACCOUNT const& field = sfSponsor);
/** Remove the reserve sponsor field from a ledger entry.
*
* A no-op when @p field is not present on @p sle. For RippleState entries
* the field must be sfHighSponsor or sfLowSponsor; for all other entry types
* it must be sfSponsor.
*
* @param sle The ledger entry to modify
* @param field The sponsor field to clear (defaults to sfSponsor)
*/
void
removeSponsorFromLedgerEntry(SLE::ref sle, SF_ACCOUNT const& field = sfSponsor);
/** Whether @p account is the owner of a ledger entry for sponsorship purposes.
*
* Ownership rules vary by entry type. For RippleState entries the owner is
* whichever side of the trust line holds the reserve. For credentials, the
* owner is the subject once accepted and the issuer before acceptance.
*
* @param view The ledger read view (used for SignerList lookup)
* @param sle The ledger entry whose owner is checked
* @param account The candidate account to match against
* @return true if @p account owns @p sle, false otherwise.
*/
bool
isLedgerEntryOwner(ReadView const& view, SLE const& sle, AccountID const& account);
/** Whether this ledger entry type can have a reserve sponsor attached to it. */
bool
isLedgerEntrySupportedBySponsorship(SLE const& sle);
/** Return the number of owner-count units the ledger entry consumes.
*
* Most entries cost 1. Exceptions: Oracles scale with their price-data series
* size, Vaults cost 2 (vault + pseudo-account), and legacy SignerList entries
* (pre-MultiSignReserve) cost 2 + signer count.
*/
std::uint32_t
getLedgerEntryOwnerCount(SLE const& sle);
/** Return the SField used to store the reserve sponsor for @p owner on @p sle.
*
* For most entry types this is sfSponsor. RippleState entries use
* sfHighSponsor or sfLowSponsor depending on which side of the trust line
* @p owner holds.
*
* @param sle The ledger entry
* @param owner The account whose sponsor field is needed
* @return sfHighSponsor, sfLowSponsor, or sfSponsor as appropriate.
*/
SF_ACCOUNT const&
getLedgerEntrySponsorField(SLE const& sle, AccountID const& owner);
} // namespace xrpl
+4 -2
View File
@@ -10,6 +10,7 @@
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
@@ -302,7 +303,7 @@ canAddHolding(ReadView const& view, Asset const& asset);
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
ApplyViewContext ctx,
AccountID const& accountID,
XRPAmount priorBalance,
Asset const& asset,
@@ -310,7 +311,7 @@ addEmptyHolding(
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
ApplyViewContext ctx,
AccountID const& accountID,
Asset const& asset,
beast::Journal journal);
@@ -371,6 +372,7 @@ accountSend(
AccountID const& to,
STAmount const& saAmount,
beast::Journal j,
SLE::ref sponsorSle = {},
WaiveTransferFee waiveFee = WaiveTransferFee::No,
AllowMPTOverflow allowOverflow = AllowMPTOverflow::No);
+2 -2
View File
@@ -74,9 +74,9 @@ operator==(Book const& lhs, Book const& rhs)
[[nodiscard]] constexpr std::weak_ordering
operator<=>(Book const& lhs, Book const& rhs)
{
if (auto const c{lhs.in <=> rhs.in}; c != 0)
if (auto const c{lhs.in <=> rhs.in}; c != 0) // NOLINT(modernize-use-nullptr)
return c;
if (auto const c{lhs.out <=> rhs.out}; c != 0)
if (auto const c{lhs.out <=> rhs.out}; c != 0) // NOLINT(modernize-use-nullptr)
return c;
// Manually compare optionals
+4 -5
View File
@@ -2,7 +2,6 @@
#include <xrpl/protocol/XRPAmount.h>
#include <cstddef>
#include <cstdint>
namespace xrpl {
@@ -39,13 +38,13 @@ struct Fees
/** Returns the account reserve given the owner count, in drops.
The reserve is calculated as the reserve base plus
the reserve increment times the number of increments.
The reserve is calculated as the reserve base times the number of accounts plus the reserve
increment times the number of increments.
*/
[[nodiscard]] XRPAmount
accountReserve(std::size_t ownerCount) const
accountReserve(std::uint32_t ownerCount, std::uint32_t accountCount) const
{
return reserve + ownerCount * increment;
return (reserve * accountCount) + (increment * ownerCount);
}
};
+4
View File
@@ -142,6 +142,10 @@ ticket(uint256 const& key)
Keylet
signerList(AccountID const& account) noexcept;
/** A Sponsorship */
Keylet
sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept;
/** A Check */
/** @{ */
Keylet
+1 -1
View File
@@ -84,7 +84,7 @@ operator==(Issue const& lhs, Issue const& rhs)
[[nodiscard]] constexpr std::weak_ordering
operator<=>(Issue const& lhs, Issue const& rhs)
{
if (auto const c{lhs.currency <=> rhs.currency}; c != 0)
if (auto const c{lhs.currency <=> rhs.currency}; c != 0) // NOLINT(modernize-use-nullptr)
return c;
if (isXRP(lhs.currency))
+5 -1
View File
@@ -208,7 +208,11 @@ enum LedgerEntryType : std::uint16_t {
LEDGER_OBJECT(Loan, \
LSF_FLAG(lsfLoanDefault, 0x00010000) \
LSF_FLAG(lsfLoanImpaired, 0x00020000) \
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */ \
\
LEDGER_OBJECT(Sponsorship, \
LSF_FLAG(lsfSponsorshipRequireSignForFee, 0x00010000) \
LSF_FLAG(lsfSponsorshipRequireSignForReserve, 0x00020000))
// clang-format on
+2 -2
View File
@@ -229,10 +229,10 @@ public:
[[nodiscard]] AccountID
getAccountID(SField const& field) const;
/** The account responsible for the fee and authorization: the delegate when
/** The account responsible for the authorization: the delegate when
sfDelegate is present, otherwise the account. */
[[nodiscard]] AccountID
getFeePayer() const;
getInitiator() const;
[[nodiscard]] Blob
getFieldVL(SField const& field) const;
+3
View File
@@ -141,6 +141,9 @@ public:
[[nodiscard]] std::vector<uint256> const&
getBatchTransactionIDs() const;
[[nodiscard]] AccountID
getFeePayerID() const;
private:
/** Check the signature.
@param rules The current ledger rules.
+2
View File
@@ -225,6 +225,7 @@ enum TERcodes : TERUnderlyingType {
// create a pseudo-account
terNO_DELEGATE_PERMISSION, // Delegate does not have permission
terLOCKED, // MPT is locked
terNO_PERMISSION, // No permission but retry
};
//------------------------------------------------------------------------------
@@ -368,6 +369,7 @@ enum TECcodes : TERUnderlyingType {
// reclaimed after those networks reset.
tecNO_DELEGATE_PERMISSION = 198,
tecBAD_PROOF = 199,
tecNO_SPONSOR_PERMISSION = 200,
};
//------------------------------------------------------------------------------
+23 -2
View File
@@ -102,7 +102,8 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TRANSACTION(Payment, \
TF_FLAG(tfNoRippleDirect, 0x00010000) \
TF_FLAG(tfPartialPayment, 0x00020000) \
TF_FLAG(tfLimitQuality, 0x00040000), \
TF_FLAG(tfLimitQuality, 0x00040000) \
TF_FLAG(tfSponsorCreatedAccount, 0x00080000), \
MASK_ADJ(0)) \
\
TRANSACTION(TrustSet, \
@@ -141,7 +142,7 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TF_FLAG(tfMPTCanTrade, lsfMPTCanTrade) \
TF_FLAG(tfMPTCanTransfer, lsfMPTCanTransfer) \
TF_FLAG(tfMPTCanClawback, lsfMPTCanClawback) \
TF_FLAG(tfMPTCanHoldConfidentialBalance, lsfMPTCanHoldConfidentialBalance), \
TF_FLAG(tfMPTCanHoldConfidentialBalance, lsfMPTCanHoldConfidentialBalance), \
MASK_ADJ(0)) \
\
TRANSACTION(MPTokenAuthorize, \
@@ -215,6 +216,20 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TF_FLAG(tfLoanDefault, 0x00010000) \
TF_FLAG(tfLoanImpair, 0x00020000) \
TF_FLAG(tfLoanUnimpair, 0x00040000), \
MASK_ADJ(0)) \
\
TRANSACTION(SponsorshipSet, \
TF_FLAG(tfSponsorshipSetRequireSignForFee, 0x00010000) \
TF_FLAG(tfSponsorshipClearRequireSignForFee, 0x00020000) \
TF_FLAG(tfSponsorshipSetRequireSignForReserve, 0x00040000) \
TF_FLAG(tfSponsorshipClearRequireSignForReserve, 0x00080000) \
TF_FLAG(tfDeleteObject, 0x00100000), \
MASK_ADJ(0)) \
\
TRANSACTION(SponsorshipTransfer, \
TF_FLAG(tfSponsorshipEnd, 0x00010000) \
TF_FLAG(tfSponsorshipCreate, 0x00020000) \
TF_FLAG(tfSponsorshipReassign, 0x00040000), \
MASK_ADJ(0))
// clang-format on
@@ -444,6 +459,12 @@ getAsfFlagMap()
#pragma pop_macro("ACCOUNTSET_FLAG_TO_MAP")
#pragma pop_macro("ACCOUNTSET_FLAGS")
// Sponsor flags (spf)
inline constexpr FlagValue spfSponsorFee = 1;
inline constexpr FlagValue spfSponsorReserve = 2;
inline constexpr FlagValue spfSponsorFlagMask = ~(spfSponsorFee | spfSponsorReserve);
} // namespace xrpl
// NOLINTEND(readability-identifier-naming)
@@ -15,6 +15,7 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocolV1_1, Supported::No, VoteBehavior::DefaultNo)
XRPL_FEATURE(ConfidentialTransfer, Supported::Yes, VoteBehavior::DefaultNo)
@@ -127,29 +127,32 @@ LEDGER_ENTRY(ltTICKET, 0x0054, Ticket, ticket, ({
\sa keylet::account
*/
LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
{sfAccount, SoeRequired},
{sfSequence, SoeRequired},
{sfBalance, SoeRequired},
{sfOwnerCount, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfAccountTxnID, SoeOptional},
{sfRegularKey, SoeOptional},
{sfEmailHash, SoeOptional},
{sfWalletLocator, SoeOptional},
{sfWalletSize, SoeOptional},
{sfMessageKey, SoeOptional},
{sfTransferRate, SoeOptional},
{sfDomain, SoeOptional},
{sfTickSize, SoeOptional},
{sfTicketCount, SoeOptional},
{sfNFTokenMinter, SoeOptional},
{sfMintedNFTokens, SoeDefault},
{sfBurnedNFTokens, SoeDefault},
{sfFirstNFTokenSequence, SoeOptional},
{sfAMMID, SoeOptional}, // pseudo-account designator
{sfVaultID, SoeOptional}, // pseudo-account designator
{sfLoanBrokerID, SoeOptional}, // pseudo-account designator
{sfAccount, SoeRequired},
{sfSequence, SoeRequired},
{sfBalance, SoeRequired},
{sfOwnerCount, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfAccountTxnID, SoeOptional},
{sfRegularKey, SoeOptional},
{sfEmailHash, SoeOptional},
{sfWalletLocator, SoeOptional},
{sfWalletSize, SoeOptional},
{sfMessageKey, SoeOptional},
{sfTransferRate, SoeOptional},
{sfDomain, SoeOptional},
{sfTickSize, SoeOptional},
{sfTicketCount, SoeOptional},
{sfNFTokenMinter, SoeOptional},
{sfMintedNFTokens, SoeDefault},
{sfBurnedNFTokens, SoeDefault},
{sfFirstNFTokenSequence, SoeOptional},
{sfSponsoredOwnerCount, SoeDefault},
{sfSponsoringOwnerCount, SoeDefault},
{sfSponsoringAccountCount, SoeDefault},
{sfAMMID, SoeOptional}, // pseudo-account designator
{sfVaultID, SoeOptional}, // pseudo-account designator
{sfLoanBrokerID, SoeOptional}, // pseudo-account designator
}))
/** A ledger object which contains a list of object identifiers.
@@ -286,6 +289,8 @@ LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, state, ({
{sfHighNode, SoeOptional},
{sfHighQualityIn, SoeOptional},
{sfHighQualityOut, SoeOptional},
{sfHighSponsor, SoeOptional},
{sfLowSponsor, SoeOptional},
}))
/** The ledger object which lists the network's fee settings.
@@ -616,5 +621,20 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
{sfLoanScale, SoeDefault},
}))
/** A ledger object representing a sponsorship.
\sa keylet::sponsorship
*/
LEDGER_ENTRY(ltSPONSORSHIP, 0x0090, Sponsorship, sponsorship, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfOwner, SoeRequired},
{sfSponsee, SoeRequired},
{sfFeeAmount, SoeOptional},
{sfMaxFee, SoeOptional},
{sfRemainingOwnerCount, SoeDefault},
{sfOwnerNode, SoeRequired},
{sfSponseeNode, SoeRequired},
}))
#undef EXPAND
#undef LEDGER_ENTRY_DUPLICATE
@@ -114,6 +114,11 @@ TYPED_SFIELD(sfLateInterestRate, UINT32, 66) // 1/10 basis points (bi
TYPED_SFIELD(sfCloseInterestRate, UINT32, 67) // 1/10 basis points (bips)
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bips)
TYPED_SFIELD(sfConfidentialBalanceVersion, UINT32, 69)
TYPED_SFIELD(sfSponsoredOwnerCount, UINT32, 70)
TYPED_SFIELD(sfSponsoringOwnerCount, UINT32, 71)
TYPED_SFIELD(sfSponsoringAccountCount, UINT32, 72)
TYPED_SFIELD(sfRemainingOwnerCount, UINT32, 73)
TYPED_SFIELD(sfSponsorFlags, UINT32, 74)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -148,6 +153,7 @@ TYPED_SFIELD(sfLockedAmount, UINT64, 29, SField::kSmdBaseTen|SFie
TYPED_SFIELD(sfVaultNode, UINT64, 30)
TYPED_SFIELD(sfLoanBrokerNode, UINT64, 31)
TYPED_SFIELD(sfConfidentialOutstandingAmount, UINT64, 32, SField::kSmdBaseTen|SField::kSmdDefault)
TYPED_SFIELD(sfSponseeNode, UINT64, 33)
// 128-bit
TYPED_SFIELD(sfEmailHash, UINT128, 1)
@@ -209,6 +215,7 @@ TYPED_SFIELD(sfLoanBrokerID, UINT256, 37,
TYPED_SFIELD(sfLoanID, UINT256, 38)
TYPED_SFIELD(sfReferenceHolding, UINT256, 39)
TYPED_SFIELD(sfBlindingFactor, UINT256, 40)
TYPED_SFIELD(sfObjectID, UINT256, 41)
// number (common)
TYPED_SFIELD(sfNumber, NUMBER, 1)
@@ -268,6 +275,8 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28)
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
TYPED_SFIELD(sfFeeAmount, AMOUNT, 32)
TYPED_SFIELD(sfMaxFee, AMOUNT, 33)
// variable length (common)
TYPED_SFIELD(sfPublicKey, VL, 1)
@@ -343,6 +352,11 @@ TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23)
TYPED_SFIELD(sfSubject, ACCOUNT, 24)
TYPED_SFIELD(sfBorrower, ACCOUNT, 25)
TYPED_SFIELD(sfCounterparty, ACCOUNT, 26)
TYPED_SFIELD(sfSponsor, ACCOUNT, 27)
TYPED_SFIELD(sfHighSponsor, ACCOUNT, 28)
TYPED_SFIELD(sfLowSponsor, ACCOUNT, 29)
TYPED_SFIELD(sfCounterpartySponsor, ACCOUNT, 30)
TYPED_SFIELD(sfSponsee, ACCOUNT, 31)
// vector of 256-bit
TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::kSmdNever)
@@ -407,6 +421,7 @@ UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34)
UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35)
UNTYPED_SFIELD(sfBook, OBJECT, 36)
UNTYPED_SFIELD(sfCounterpartySignature, OBJECT, 37, SField::kSmdDefault, SField::kNotSigning)
UNTYPED_SFIELD(sfSponsorSignature, OBJECT, 38, SField::kSmdDefault, SField::kNotSigning)
// array of objects (common)
// ARRAY/1 is reserved for end of array
@@ -1165,6 +1165,35 @@ TRANSACTION(ttCONFIDENTIAL_MPT_CLAWBACK, 89, ConfidentialMPTClawback,
{sfZKProof, SoeRequired},
}))
/** This transaction transfers sponsorship on an object/account. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/sponsor/SponsorshipTransfer.h>
#endif
TRANSACTION(ttSPONSORSHIP_TRANSFER, 90, SponsorshipTransfer,
Delegation::NotDelegable,
featureSponsor,
NoPriv,
({
{sfObjectID, SoeOptional},
{sfSponsee, SoeOptional},
}))
/** This transaction creates a Sponsorship object. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/sponsor/SponsorshipSet.h>
#endif
TRANSACTION(ttSPONSORSHIP_SET, 91, SponsorshipSet,
Delegation::Delegable,
featureSponsor,
NoPriv,
({
{sfCounterpartySponsor, SoeOptional},
{sfSponsee, SoeOptional},
{sfFeeAmount, SoeOptional},
{sfMaxFee, SoeOptional},
{sfRemainingOwnerCount, SoeOptional},
}))
/** This system-generated transaction type is used to update the status of the various amendments.
For details, see: https://xrpl.org/amendments.html
+3
View File
@@ -558,6 +558,9 @@ JSS(source_account); // in: PathRequest, RipplePathFind
JSS(source_amount); // in: PathRequest, RipplePathFind
JSS(source_currencies); // in: PathRequest, RipplePathFind
JSS(source_tag); // out: AccountChannels
JSS(sponsee); // in: LedgerEntry
JSS(sponsor); // in: LedgerEntry
JSS(sponsored); // in: AccountObjects
JSS(stand_alone); // out: NetworkOPs
JSS(standard_deviation); // out: get_aggregate_price
JSS(start); // in: TxHistory
@@ -447,6 +447,78 @@ public:
return this->sle_->isFieldPresent(sfFirstNFTokenSequence);
}
/**
* @brief Get sfSponsoredOwnerCount (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSponsoredOwnerCount() const
{
if (hasSponsoredOwnerCount())
return this->sle_->at(sfSponsoredOwnerCount);
return std::nullopt;
}
/**
* @brief Check if sfSponsoredOwnerCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsoredOwnerCount() const
{
return this->sle_->isFieldPresent(sfSponsoredOwnerCount);
}
/**
* @brief Get sfSponsoringOwnerCount (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSponsoringOwnerCount() const
{
if (hasSponsoringOwnerCount())
return this->sle_->at(sfSponsoringOwnerCount);
return std::nullopt;
}
/**
* @brief Check if sfSponsoringOwnerCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsoringOwnerCount() const
{
return this->sle_->isFieldPresent(sfSponsoringOwnerCount);
}
/**
* @brief Get sfSponsoringAccountCount (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSponsoringAccountCount() const
{
if (hasSponsoringAccountCount())
return this->sle_->at(sfSponsoringAccountCount);
return std::nullopt;
}
/**
* @brief Check if sfSponsoringAccountCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsoringAccountCount() const
{
return this->sle_->isFieldPresent(sfSponsoringAccountCount);
}
/**
* @brief Get sfAMMID (SoeOptional)
* @return The field value, or std::nullopt if not present.
@@ -786,6 +858,39 @@ public:
return *this;
}
/**
* @brief Set sfSponsoredOwnerCount (SoeDefault)
* @return Reference to this builder for method chaining.
*/
AccountRootBuilder&
setSponsoredOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSponsoredOwnerCount] = value;
return *this;
}
/**
* @brief Set sfSponsoringOwnerCount (SoeDefault)
* @return Reference to this builder for method chaining.
*/
AccountRootBuilder&
setSponsoringOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSponsoringOwnerCount] = value;
return *this;
}
/**
* @brief Set sfSponsoringAccountCount (SoeDefault)
* @return Reference to this builder for method chaining.
*/
AccountRootBuilder&
setSponsoringAccountCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSponsoringAccountCount] = value;
return *this;
}
/**
* @brief Set sfAMMID (SoeOptional)
* @return Reference to this builder for method chaining.
@@ -243,6 +243,54 @@ public:
{
return this->sle_->isFieldPresent(sfHighQualityOut);
}
/**
* @brief Get sfHighSponsor (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getHighSponsor() const
{
if (hasHighSponsor())
return this->sle_->at(sfHighSponsor);
return std::nullopt;
}
/**
* @brief Check if sfHighSponsor is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasHighSponsor() const
{
return this->sle_->isFieldPresent(sfHighSponsor);
}
/**
* @brief Get sfLowSponsor (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getLowSponsor() const
{
if (hasLowSponsor())
return this->sle_->at(sfLowSponsor);
return std::nullopt;
}
/**
* @brief Check if sfLowSponsor is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasLowSponsor() const
{
return this->sle_->isFieldPresent(sfLowSponsor);
}
};
/**
@@ -410,6 +458,28 @@ public:
return *this;
}
/**
* @brief Set sfHighSponsor (SoeOptional)
* @return Reference to this builder for method chaining.
*/
RippleStateBuilder&
setHighSponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfHighSponsor] = value;
return *this;
}
/**
* @brief Set sfLowSponsor (SoeOptional)
* @return Reference to this builder for method chaining.
*/
RippleStateBuilder&
setLowSponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfLowSponsor] = value;
return *this;
}
/**
* @brief Build and return the completed RippleState wrapper.
* @param index The ledger entry index.
@@ -0,0 +1,344 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::ledger_entries {
class SponsorshipBuilder;
/**
* @brief Ledger Entry: Sponsorship
*
* Type: ltSPONSORSHIP (0x0090)
* RPC Name: sponsorship
*
* Immutable wrapper around SLE providing type-safe field access.
* Use SponsorshipBuilder to construct new ledger entries.
*/
class Sponsorship : public LedgerEntryBase
{
public:
static constexpr LedgerEntryType entryType = ltSPONSORSHIP;
/**
* @brief Construct a Sponsorship ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Sponsorship(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
if (sle_->getType() != entryType)
{
throw std::runtime_error("Invalid ledger entry type for Sponsorship");
}
}
// Ledger entry-specific field getters
/**
* @brief Get sfPreviousTxnID (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getPreviousTxnID() const
{
return this->sle_->at(sfPreviousTxnID);
}
/**
* @brief Get sfPreviousTxnLgrSeq (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getPreviousTxnLgrSeq() const
{
return this->sle_->at(sfPreviousTxnLgrSeq);
}
/**
* @brief Get sfOwner (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getOwner() const
{
return this->sle_->at(sfOwner);
}
/**
* @brief Get sfSponsee (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getSponsee() const
{
return this->sle_->at(sfSponsee);
}
/**
* @brief Get sfFeeAmount (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getFeeAmount() const
{
if (hasFeeAmount())
return this->sle_->at(sfFeeAmount);
return std::nullopt;
}
/**
* @brief Check if sfFeeAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasFeeAmount() const
{
return this->sle_->isFieldPresent(sfFeeAmount);
}
/**
* @brief Get sfMaxFee (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getMaxFee() const
{
if (hasMaxFee())
return this->sle_->at(sfMaxFee);
return std::nullopt;
}
/**
* @brief Check if sfMaxFee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasMaxFee() const
{
return this->sle_->isFieldPresent(sfMaxFee);
}
/**
* @brief Get sfRemainingOwnerCount (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getRemainingOwnerCount() const
{
if (hasRemainingOwnerCount())
return this->sle_->at(sfRemainingOwnerCount);
return std::nullopt;
}
/**
* @brief Check if sfRemainingOwnerCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasRemainingOwnerCount() const
{
return this->sle_->isFieldPresent(sfRemainingOwnerCount);
}
/**
* @brief Get sfOwnerNode (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getOwnerNode() const
{
return this->sle_->at(sfOwnerNode);
}
/**
* @brief Get sfSponseeNode (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getSponseeNode() const
{
return this->sle_->at(sfSponseeNode);
}
};
/**
* @brief Builder for Sponsorship ledger entries.
*
* Provides a fluent interface for constructing ledger entries with method chaining.
* Uses STObject internally for flexible ledger entry construction.
* Inherits common field setters from LedgerEntryBuilderBase.
*/
class SponsorshipBuilder : public LedgerEntryBuilderBase<SponsorshipBuilder>
{
public:
/**
* @brief Construct a new SponsorshipBuilder with required fields.
* @param previousTxnID The sfPreviousTxnID field value.
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
* @param owner The sfOwner field value.
* @param sponsee The sfSponsee field value.
* @param ownerNode The sfOwnerNode field value.
* @param sponseeNode The sfSponseeNode field value.
*/
SponsorshipBuilder(std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_ACCOUNT::type::value_type> const& owner,std::decay_t<typename SF_ACCOUNT::type::value_type> const& sponsee,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_UINT64::type::value_type> const& sponseeNode)
: LedgerEntryBuilderBase<SponsorshipBuilder>(ltSPONSORSHIP)
{
setPreviousTxnID(previousTxnID);
setPreviousTxnLgrSeq(previousTxnLgrSeq);
setOwner(owner);
setSponsee(sponsee);
setOwnerNode(ownerNode);
setSponseeNode(sponseeNode);
}
/**
* @brief Construct a SponsorshipBuilder from an existing SLE object.
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
SponsorshipBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltSPONSORSHIP)
{
throw std::runtime_error("Invalid ledger entry type for Sponsorship");
}
object_ = *sle;
}
/** @brief Ledger entry-specific field setters */
/**
* @brief Set sfPreviousTxnID (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfPreviousTxnID] = value;
return *this;
}
/**
* @brief Set sfPreviousTxnLgrSeq (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfPreviousTxnLgrSeq] = value;
return *this;
}
/**
* @brief Set sfOwner (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setOwner(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfOwner] = value;
return *this;
}
/**
* @brief Set sfSponsee (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfSponsee] = value;
return *this;
}
/**
* @brief Set sfFeeAmount (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfFeeAmount] = value;
return *this;
}
/**
* @brief Set sfMaxFee (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setMaxFee(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfMaxFee] = value;
return *this;
}
/**
* @brief Set sfRemainingOwnerCount (SoeDefault)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setRemainingOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfRemainingOwnerCount] = value;
return *this;
}
/**
* @brief Set sfOwnerNode (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfOwnerNode] = value;
return *this;
}
/**
* @brief Set sfSponseeNode (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setSponseeNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfSponseeNode] = value;
return *this;
}
/**
* @brief Build and return the completed Sponsorship wrapper.
* @param index The ledger entry index.
* @return The constructed ledger entry wrapper.
*/
Sponsorship
build(uint256 const& index)
{
return Sponsorship{std::make_shared<SLE>(std::move(object_), index)};
}
};
} // namespace xrpl::ledger_entries
@@ -0,0 +1,290 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class SponsorshipSetBuilder;
/**
* @brief Transaction: SponsorshipSet
*
* Type: ttSPONSORSHIP_SET (91)
* Delegable: Delegation::Delegable
* Amendment: featureSponsor
* Privileges: NoPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use SponsorshipSetBuilder to construct new transactions.
*/
class SponsorshipSet : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttSPONSORSHIP_SET;
/**
* @brief Construct a SponsorshipSet transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit SponsorshipSet(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for SponsorshipSet");
}
}
// Transaction-specific field getters
/**
* @brief Get sfCounterpartySponsor (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getCounterpartySponsor() const
{
if (hasCounterpartySponsor())
{
return this->tx_->at(sfCounterpartySponsor);
}
return std::nullopt;
}
/**
* @brief Check if sfCounterpartySponsor is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasCounterpartySponsor() const
{
return this->tx_->isFieldPresent(sfCounterpartySponsor);
}
/**
* @brief Get sfSponsee (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getSponsee() const
{
if (hasSponsee())
{
return this->tx_->at(sfSponsee);
}
return std::nullopt;
}
/**
* @brief Check if sfSponsee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsee() const
{
return this->tx_->isFieldPresent(sfSponsee);
}
/**
* @brief Get sfFeeAmount (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getFeeAmount() const
{
if (hasFeeAmount())
{
return this->tx_->at(sfFeeAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfFeeAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasFeeAmount() const
{
return this->tx_->isFieldPresent(sfFeeAmount);
}
/**
* @brief Get sfMaxFee (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getMaxFee() const
{
if (hasMaxFee())
{
return this->tx_->at(sfMaxFee);
}
return std::nullopt;
}
/**
* @brief Check if sfMaxFee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasMaxFee() const
{
return this->tx_->isFieldPresent(sfMaxFee);
}
/**
* @brief Get sfRemainingOwnerCount (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getRemainingOwnerCount() const
{
if (hasRemainingOwnerCount())
{
return this->tx_->at(sfRemainingOwnerCount);
}
return std::nullopt;
}
/**
* @brief Check if sfRemainingOwnerCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasRemainingOwnerCount() const
{
return this->tx_->isFieldPresent(sfRemainingOwnerCount);
}
};
/**
* @brief Builder for SponsorshipSet transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class SponsorshipSetBuilder : public TransactionBuilderBase<SponsorshipSetBuilder>
{
public:
/**
* @brief Construct a new SponsorshipSetBuilder with required fields.
* @param account The account initiating the transaction.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
SponsorshipSetBuilder(SF_ACCOUNT::type::value_type account,
std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<SponsorshipSetBuilder>(ttSPONSORSHIP_SET, account, sequence, fee)
{
}
/**
* @brief Construct a SponsorshipSetBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
SponsorshipSetBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttSPONSORSHIP_SET)
{
throw std::runtime_error("Invalid transaction type for SponsorshipSetBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfCounterpartySponsor (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setCounterpartySponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfCounterpartySponsor] = value;
return *this;
}
/**
* @brief Set sfSponsee (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfSponsee] = value;
return *this;
}
/**
* @brief Set sfFeeAmount (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfFeeAmount] = value;
return *this;
}
/**
* @brief Set sfMaxFee (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setMaxFee(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfMaxFee] = value;
return *this;
}
/**
* @brief Set sfRemainingOwnerCount (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setRemainingOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfRemainingOwnerCount] = value;
return *this;
}
/**
* @brief Build and return the SponsorshipSet wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
SponsorshipSet
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return SponsorshipSet{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions
@@ -0,0 +1,179 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class SponsorshipTransferBuilder;
/**
* @brief Transaction: SponsorshipTransfer
*
* Type: ttSPONSORSHIP_TRANSFER (90)
* Delegable: Delegation::NotDelegable
* Amendment: featureSponsor
* Privileges: NoPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use SponsorshipTransferBuilder to construct new transactions.
*/
class SponsorshipTransfer : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttSPONSORSHIP_TRANSFER;
/**
* @brief Construct a SponsorshipTransfer transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit SponsorshipTransfer(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for SponsorshipTransfer");
}
}
// Transaction-specific field getters
/**
* @brief Get sfObjectID (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT256::type::value_type>
getObjectID() const
{
if (hasObjectID())
{
return this->tx_->at(sfObjectID);
}
return std::nullopt;
}
/**
* @brief Check if sfObjectID is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasObjectID() const
{
return this->tx_->isFieldPresent(sfObjectID);
}
/**
* @brief Get sfSponsee (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getSponsee() const
{
if (hasSponsee())
{
return this->tx_->at(sfSponsee);
}
return std::nullopt;
}
/**
* @brief Check if sfSponsee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsee() const
{
return this->tx_->isFieldPresent(sfSponsee);
}
};
/**
* @brief Builder for SponsorshipTransfer transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class SponsorshipTransferBuilder : public TransactionBuilderBase<SponsorshipTransferBuilder>
{
public:
/**
* @brief Construct a new SponsorshipTransferBuilder with required fields.
* @param account The account initiating the transaction.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
SponsorshipTransferBuilder(SF_ACCOUNT::type::value_type account,
std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<SponsorshipTransferBuilder>(ttSPONSORSHIP_TRANSFER, account, sequence, fee)
{
}
/**
* @brief Construct a SponsorshipTransferBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
SponsorshipTransferBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttSPONSORSHIP_TRANSFER)
{
throw std::runtime_error("Invalid transaction type for SponsorshipTransferBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfObjectID (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipTransferBuilder&
setObjectID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfObjectID] = value;
return *this;
}
/**
* @brief Set sfSponsee (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipTransferBuilder&
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfSponsee] = value;
return *this;
}
/**
* @brief Build and return the SponsorshipTransfer wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
SponsorshipTransfer
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return SponsorshipTransfer{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions
+9
View File
@@ -127,6 +127,15 @@ public:
TER
checkInvariants(TER const result, XRPAmount const fee);
ApplyViewContext
getApplyViewContext()
{
XRPL_ASSERT(
view_.has_value(),
"xrpl::ApplyContext::getApplyViewContext : view_ emplaced in constructor");
return {.view = *view_, .tx = tx};
}
private:
static TER
failInvariantCheck(TER const result);
+24
View File
@@ -10,8 +10,10 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/Units.h>
@@ -126,6 +128,21 @@ struct PreflightResult;
// Needed for preflight specialization
class Change;
enum class FeePayerType {
Account,
Delegate,
SponsorCoSigned,
SponsorPreFunded,
};
struct FeePayer
{
AccountID id;
Keylet keylet;
SF_AMOUNT const& balanceField;
FeePayerType type{FeePayerType::Account};
};
class Transactor
{
protected:
@@ -298,6 +315,10 @@ public:
return T::checkGranularSemantics(view, tx, heldGranularPermissions);
}
static NotTEC
checkSponsor(ReadView const& view, STTx const& tx);
/////////////////////////////////////////////////////
// Interface used by AccountDelete
@@ -459,6 +480,9 @@ private:
std::pair<TER, XRPAmount>
reset(XRPAmount fee);
static FeePayer
getFeePayer(ReadView const& view, STTx const& tx);
TER
consumeSeqProxy(SLE::pointer const& sleAccount);
TER
+4 -1
View File
@@ -15,6 +15,7 @@
#include <xrpl/tx/invariants/NFTInvariant.h>
#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
#include <xrpl/tx/invariants/SponsorshipInvariant.h>
#include <xrpl/tx/invariants/VaultInvariant.h>
#include <cstdint>
@@ -441,7 +442,9 @@ using InvariantChecks = std::tuple<
ValidMPTPayment,
ValidAmounts,
ValidMPTTransfer,
ObjectHasPseudoAccount>;
ObjectHasPseudoAccount,
SponsorshipOwnerCountsMatch,
SponsorshipAccountCountMatchesField>;
/**
* @brief get a tuple of all invariant checks
@@ -0,0 +1,61 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <cstdint>
namespace xrpl {
/**
* @brief Invariant: Sponsored owner counts are balanced.
*
* The following checks are made for every transaction:
* - The sum of all per-account deltas of `sfSponsoredOwnerCount` equals
* the sum of all per-account deltas of `sfSponsoringOwnerCount`.
* - Account OwnerCount must be greater than or equal to SponsoredOwnerCount.
* - The net delta of sponsored object owner counts (the owner-count
* magnitude of sponsored ledger entries) equals the net delta of
* `sfSponsoredOwnerCount`.
*/
class SponsorshipOwnerCountsMatch
{
std::int64_t deltaSponsoredOwnerCount_ = 0;
std::int64_t deltaSponsoringOwnerCount_ = 0;
std::int64_t deltaSponsoredObjectOwnerCount_ = 0;
std::uint64_t ownerCountBelowSponsored_ = 0;
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
};
/**
* @brief Invariant: Sponsoring account relationships tracked consistently.
*
* The following check is made for every transaction:
* - The net delta of `sfSponsoringAccountCount` across all accounts equals
* the net delta of the count of ltACCOUNT_ROOT entries having
* `sfSponsor` present (presence transitions only: add/remove).
*/
class SponsorshipAccountCountMatchesField
{
std::int64_t deltaSponsoringAccountCount_ = 0;
std::int64_t deltaSponsorFieldPresence_ = 0;
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
};
} // namespace xrpl
+5 -1
View File
@@ -7,6 +7,7 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
@@ -110,7 +111,10 @@ public:
send(Args&&... args)
{
return accountSend(
std::forward<Args>(args)..., WaiveTransferFee::Yes, AllowMPTOverflow::Yes);
std::forward<Args>(args)...,
SLE::pointer(),
WaiveTransferFee::Yes,
AllowMPTOverflow::Yes);
}
[[nodiscard]] bool
+2 -1
View File
@@ -234,7 +234,8 @@ template <typename... Args>
TER
TOffer<TIn, TOut>::send(Args&&... args)
{
return accountSend(std::forward<Args>(args)..., WaiveTransferFee::No, AllowMPTOverflow::Yes);
return accountSend(
std::forward<Args>(args)..., SLE::pointer(), WaiveTransferFee::No, AllowMPTOverflow::Yes);
}
template <StepAmount TIn, StepAmount TOut>
@@ -0,0 +1,52 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/Transactor.h>
#include <cstdint>
namespace xrpl {
class SponsorshipSet : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit SponsorshipSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
void
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl
@@ -0,0 +1,49 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/Transactor.h>
#include <cstdint>
namespace xrpl {
class SponsorshipTransfer : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit SponsorshipTransfer(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
void
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl
@@ -76,7 +76,7 @@ public:
beast::Journal const& j) override;
static std::expected<MPTID, TER>
create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args);
create(ApplyViewContext ctx, beast::Journal journal, MPTCreateArgs const& args);
};
} // namespace xrpl
+8 -7
View File
@@ -3,6 +3,7 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/OwnerCounts.h>
#include <xrpl/ledger/RawView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Issue.h>
@@ -170,7 +171,7 @@ DeferredCredits::issuerSelfDebitMPT(
}
void
DeferredCredits::ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next)
DeferredCredits::ownerCount(AccountID const& id, OwnerCounts const& cur, OwnerCounts const& next)
{
auto const v = std::max(cur, next);
auto r = ownerCounts_.emplace(id, v);
@@ -181,7 +182,7 @@ DeferredCredits::ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_
}
}
std::optional<std::uint32_t>
std::optional<OwnerCounts>
DeferredCredits::ownerCount(AccountID const& id) const
{
auto i = ownerCounts_.find(id);
@@ -391,10 +392,10 @@ PaymentSandbox::balanceHookSelfIssueMPT(xrpl::MPTIssue const& issue, std::int64_
return STAmount{issue};
}
std::uint32_t
PaymentSandbox::ownerCountHook(AccountID const& account, std::uint32_t count) const
OwnerCounts
PaymentSandbox::ownerCountHook(AccountID const& account, OwnerCounts const& count) const
{
std::uint32_t result = count;
OwnerCounts result = count;
for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
{
if (auto adj = curSB->tab_.ownerCount(account))
@@ -442,8 +443,8 @@ PaymentSandbox::issuerSelfDebitHookMPT(
void
PaymentSandbox::adjustOwnerCountHook(
AccountID const& account,
std::uint32_t cur,
std::uint32_t next)
OwnerCounts const& cur,
OwnerCounts const& next)
{
tab_.ownerCount(account, cur, next);
}
+17 -7
View File
@@ -14,6 +14,7 @@
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -431,8 +432,7 @@ canWithdraw(ReadView const& view, STTx const& tx)
TER
doWithdraw(
ApplyView& view,
STTx const& tx,
ApplyViewContext ctx,
AccountID const& senderAcct,
AccountID const& dstAcct,
AccountID const& sourceAcct,
@@ -440,23 +440,24 @@ doWithdraw(
STAmount const& amount,
beast::Journal j)
{
auto const dstSle = ctx.view.read(keylet::account(dstAcct));
// Create trust line or MPToken for the receiving account
if (dstAcct == senderAcct)
{
if (auto const ter = addEmptyHolding(view, senderAcct, priorBalance, amount.asset(), j);
if (auto const ter = addEmptyHolding(ctx, senderAcct, priorBalance, amount.asset(), j);
!isTesSuccess(ter) && ter != tecDUPLICATE)
return ter;
}
else
{
auto dstSle = view.read(keylet::account(dstAcct));
if (auto err = verifyDepositPreauth(tx, view, senderAcct, dstAcct, dstSle, j))
if (auto err = verifyDepositPreauth(ctx.tx, ctx.view, senderAcct, dstAcct, dstSle, j))
return err;
}
// Sanity check
if (accountHolds(
view,
ctx.view,
sourceAcct,
amount.asset(),
FreezeHandling::IgnoreFreeze,
@@ -469,9 +470,18 @@ doWithdraw(
// LCOV_EXCL_STOP
}
// A reserve sponsor only covers tx.Account's own objects, so resolve the
// sponsor against the destination. accountSend can auto-create a holding
// for dstAcct; keying on the destination ensures a third-party destination's
// holding is never stamped with the tx's reserve sponsor.
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, dstSle);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
// Move the funds directly from the broker's pseudo-account to the
// dstAcct
return accountSend(view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes);
return accountSend(
ctx.view, sourceAcct, dstAcct, amount, j, *sponsorSle, WaiveTransferFee::Yes);
}
TER
+384 -25
View File
@@ -7,7 +7,9 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/OwnerCounts.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -15,6 +17,7 @@
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol/digest.h>
@@ -41,46 +44,226 @@ isGlobalFrozen(ReadView const& view, AccountID const& issuer)
return false;
}
namespace {
// An owner count cannot be negative. If adjustment would cause a negative
// owner count, clamp the owner count at 0. Similarly for overflow. This
// adjustment allows the ownerCount to be adjusted up or down in multiple steps.
// If id != std::nullopt, then do error reporting.
//
// Returns adjusted owner count.
static std::uint32_t
std::uint32_t
confineOwnerCount(
std::uint32_t current,
std::int32_t adjustment,
std::uint32_t currentOwnerCount,
std::int32_t ownerCountAdj,
std::optional<AccountID> const& id = std::nullopt,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
{
std::uint32_t adjusted{current + adjustment};
if (adjustment > 0)
std::uint32_t totalOwnerCount{currentOwnerCount + ownerCountAdj};
if (ownerCountAdj > 0)
{
// Overflow is well defined on unsigned
if (adjusted < current)
if (totalOwnerCount < currentOwnerCount)
{
// LCOV_EXCL_START
if (id)
{
JLOG(j.fatal()) << "Account " << *id << " owner count exceeds max!";
}
adjusted = std::numeric_limits<std::uint32_t>::max();
totalOwnerCount = std::numeric_limits<std::uint32_t>::max();
// LCOV_EXCL_STOP
}
}
else
{
// Underflow is well defined on unsigned
if (adjusted > current)
if (totalOwnerCount > currentOwnerCount)
{
// LCOV_EXCL_START
if (id)
{
JLOG(j.fatal()) << "Account " << *id << " owner count set below 0!";
}
adjusted = 0;
totalOwnerCount = 0;
XRPL_ASSERT(!id, "xrpl::confineOwnerCount : id is not set");
// LCOV_EXCL_STOP
}
}
return adjusted;
return totalOwnerCount;
}
// Returns the number of account reserves funded by this account: 1 for itself (0 if sponsored by
// another account) plus the count of accounts it sponsors.
std::uint32_t
accountCountImpl(SLE::const_ref sle, std::int32_t accountCountAdj, beast::Journal j)
{
bool const isSponsored = sle->isFieldPresent(sfSponsor);
std::int64_t const sponsoringAccountCount = sle->getFieldU32(sfSponsoringAccountCount);
std::int64_t const currentAccountCount = (isSponsored ? 0 : 1) + sponsoringAccountCount;
std::int64_t totalAccountCount{currentAccountCount + accountCountAdj};
if (totalAccountCount > std::numeric_limits<std::uint32_t>::max())
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Reserve count exceeds max!";
totalAccountCount = std::numeric_limits<std::uint32_t>::max();
// LCOV_EXCL_STOP
}
else if (totalAccountCount < 0)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::accountCountImpl : Reserve count set below 0");
JLOG(j.fatal()) << "Reserve count set below 0";
totalAccountCount = 0;
// LCOV_EXCL_STOP
}
return totalAccountCount;
}
std::uint32_t
adjustOwnerCountImpl(
ApplyView& view,
SLE::ref sle,
SF_UINT32 const& sfield,
AccountID const& accID,
std::int32_t ownerCountAdj,
beast::Journal j)
{
std::uint32_t const currentOwnerCount = sle->at(sfield);
std::uint32_t const totalOwnerCount =
confineOwnerCount(currentOwnerCount, ownerCountAdj, accID, j);
sle->at(sfield) = totalOwnerCount;
view.update(sle);
return totalOwnerCount;
}
void
adjustOwnerCountSigned(
ApplyView& view,
SLE::ref accountSle,
SLE::ref sponsorSle,
std::int32_t adjustment,
beast::Journal j)
{
if (view.rules().enabled(featureSponsor))
{
XRPL_ASSERT(accountSle, "xrpl::adjustOwnerCountSigned : valid account sle");
if (!accountSle)
return; // LCOV_EXCL_LINE
auto const accountID = accountSle->getAccountID(sfAccount);
bool const validType = accountSle->getType() == ltACCOUNT_ROOT;
XRPL_ASSERT(validType, "xrpl::adjustOwnerCountSigned : valid account sle type");
if (!validType)
return; // LCOV_EXCL_LINE
XRPL_ASSERT(adjustment, "xrpl::adjustOwnerCountSigned : nonzero adjustment input");
OwnerCounts const currentOwnerCount(accountSle);
OwnerCounts totalOwnerCount(currentOwnerCount);
if (sponsorSle)
{
bool const validSponsorType = sponsorSle->getType() == ltACCOUNT_ROOT;
XRPL_ASSERT(validSponsorType, "xrpl::adjustOwnerCountSigned : valid sponsor sle type");
if (!validSponsorType)
return; // LCOV_EXCL_LINE
auto const sponsorID = sponsorSle->getAccountID(sfAccount);
totalOwnerCount.sponsored = adjustOwnerCountImpl(
view, accountSle, sfSponsoredOwnerCount, accountID, adjustment, j);
{
OwnerCounts const sponsorCurrent(sponsorSle);
OwnerCounts sponsorAdjustment(sponsorCurrent);
sponsorAdjustment.sponsoring = adjustOwnerCountImpl(
view, sponsorSle, sfSponsoringOwnerCount, sponsorID, adjustment, j);
view.adjustOwnerCountHook(sponsorID, sponsorCurrent, sponsorAdjustment);
}
auto sponsorshipSle = view.peek(keylet::sponsorship(sponsorID, accountID));
if (sponsorshipSle && adjustment > 0)
{
// Only decrease the pre-funded ReserveCount on Sponsorship if we assign new
// objects. Removing/reassigning ownership of the object doesn't increase
// RemainingOwnerCount back. Don't call hook because this counter is not something
// that requires reserve (like other sf...OwnerCounts do).
adjustOwnerCountImpl(
view, sponsorshipSle, sfRemainingOwnerCount, sponsorID, -adjustment, j);
}
}
totalOwnerCount.owner =
adjustOwnerCountImpl(view, accountSle, sfOwnerCount, accountID, adjustment, j);
view.adjustOwnerCountHook(accountID, currentOwnerCount, totalOwnerCount);
}
else
{
XRPL_ASSERT(accountSle, "xrpl::adjustOwnerCountSigned : valid account sle");
if (!accountSle)
return;
// the remaining are only asserts to preserve existing behavior
XRPL_ASSERT(sponsorSle == nullptr, "xrpl::adjustOwnerCountSigned : sponsor not enabled");
XRPL_ASSERT(
accountSle->getType() == ltACCOUNT_ROOT,
"xrpl::adjustOwnerCountSigned : valid account sle type");
XRPL_ASSERT(adjustment, "xrpl::adjustOwnerCount : nonzero adjustment input");
std::uint32_t const current{accountSle->getFieldU32(sfOwnerCount)};
AccountID const id = (*accountSle)[sfAccount];
std::uint32_t const adjusted = confineOwnerCount(current, adjustment, id, j);
OwnerCounts const currentOwnerCount(accountSle);
OwnerCounts finalOwnerCount(currentOwnerCount);
finalOwnerCount.owner = adjusted;
view.adjustOwnerCountHook(id, currentOwnerCount, finalOwnerCount);
accountSle->at(sfOwnerCount) = adjusted;
view.update(accountSle);
}
}
} // namespace
std::uint32_t
ownerCount(SLE::const_ref sle, beast::Journal j, std::int32_t ownerCountAdj)
{
XRPL_ASSERT(sle && sle->getType() == ltACCOUNT_ROOT, "xrpl::ownerCount : sle is account root");
AccountID const id = sle->getAccountID(sfAccount);
std::uint32_t const currentOwnerCount = sle->at(sfOwnerCount);
std::uint32_t const sponsoredOwnerCount = sle->at(sfSponsoredOwnerCount);
std::uint32_t const sponsoringOwnerCount = sle->at(sfSponsoringOwnerCount);
XRPL_ASSERT(
currentOwnerCount >= sponsoredOwnerCount,
"xrpl::ownerCount : OwnerCount must be greater than or equal to SponsoredOwnerCount");
std::int64_t deltaCount =
static_cast<std::int64_t>(ownerCountAdj) - sponsoredOwnerCount + sponsoringOwnerCount;
if (deltaCount > std::numeric_limits<std::int32_t>::max())
{
// LCOV_EXCL_START
deltaCount = std::numeric_limits<std::int32_t>::max();
JLOG(j.fatal()) << "Account " << id << " delta count exceeds max, "
<< "adjustment: " << ownerCountAdj
<< ", sponsoredCount: " << sponsoredOwnerCount
<< ", sponsoringOwnerCount: " << sponsoringOwnerCount;
// LCOV_EXCL_STOP
}
else if (deltaCount < std::numeric_limits<std::int32_t>::min())
{
// LCOV_EXCL_START
deltaCount = std::numeric_limits<std::int32_t>::min();
JLOG(j.fatal()) << "Account " << id << " delta count is below min, "
<< "adjustment: " << ownerCountAdj
<< ", sponsoredCount: " << sponsoredOwnerCount
<< ", sponsoringCount: " << sponsoringOwnerCount;
// LCOV_EXCL_STOP
}
return confineOwnerCount(currentOwnerCount, deltaCount);
}
XRPAmount
@@ -91,12 +274,14 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
return beast::kZero;
// Return balance minus reserve
std::uint32_t const ownerCount =
confineOwnerCount(view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
std::uint32_t const currentOwnerCount =
confineOwnerCount(view.ownerCountHook(id, OwnerCounts(sle)).count(), ownerCountAdj);
std::uint32_t const currentAccountCount = accountCountImpl(sle, 0, j);
// Pseudo-accounts have no reserve requirement
auto const reserve =
isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount);
auto const reserve = isPseudoAccount(sle)
? XRPAmount{0}
: view.fees().accountReserve(currentOwnerCount, currentAccountCount);
auto const fullBalance = sle->getFieldAmount(sfBalance);
@@ -108,7 +293,7 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
<< " amount=" << amount.getFullText()
<< " fullBalance=" << fullBalance.getFullText()
<< " balance=" << balance.getFullText() << " reserve=" << reserve
<< " ownerCount=" << ownerCount << " ownerCountAdj=" << ownerCountAdj;
<< " ownerCount=" << currentOwnerCount << " ownerCountAdj=" << ownerCountAdj;
return amount.xrp();
}
@@ -125,19 +310,193 @@ transferRate(ReadView const& view, AccountID const& issuer)
}
void
adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j)
increaseOwnerCount(
ApplyView& view,
SLE::ref accountSle,
SLE::ref sponsorSle,
std::uint32_t count,
beast::Journal j)
{
if (!sle)
return;
XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
AccountID const id = (*sle)[sfAccount];
std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
view.adjustOwnerCountHook(id, current, adjusted);
sle->at(sfOwnerCount) = adjusted;
view.update(sle);
XRPL_ASSERT(
count != 0 && count <= std::numeric_limits<std::int32_t>::max(),
"xrpl::increaseOwnerCount : count in signed delta range");
if (count == 0 || count > std::numeric_limits<std::int32_t>::max())
return; // LCOV_EXCL_LINE
adjustOwnerCountSigned(view, accountSle, sponsorSle, static_cast<std::int32_t>(count), j);
}
void
increaseOwnerCount(ApplyViewContext ctx, SLE::ref accountSle, std::uint32_t count, beast::Journal j)
{
auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, accountSle);
// The sponsor's existence is validated by checkReserve/checkSponsor before
// any owner-count mutation, so loading it here cannot fail.
XRPL_ASSERT(
sponsorExp.has_value(), "xrpl::increaseOwnerCount : sponsor validated before mutation");
increaseOwnerCount(ctx.view, accountSle, sponsorExp ? *sponsorExp : SLE::pointer(), count, j);
}
void
decreaseOwnerCount(
ApplyView& view,
SLE::ref accountSle,
SLE::ref sponsorSle,
std::uint32_t count,
beast::Journal j)
{
XRPL_ASSERT(
count != 0 && count <= std::numeric_limits<std::int32_t>::max(),
"xrpl::decreaseOwnerCount : count in signed delta range");
if (count == 0 || count > std::numeric_limits<std::int32_t>::max())
return; // LCOV_EXCL_LINE
adjustOwnerCountSigned(view, accountSle, sponsorSle, -static_cast<std::int32_t>(count), j);
}
void
decreaseOwnerCountForObject(
ApplyView& view,
SLE::ref accountSle,
SLE::ref objectSle,
std::uint32_t count,
beast::Journal j)
{
XRPL_ASSERT(objectSle, "xrpl::decreaseOwnerCountForObject : valid object sle");
if (!objectSle)
return; // LCOV_EXCL_LINE
bool const validObjectType = objectSle->getType() != ltACCOUNT_ROOT;
XRPL_ASSERT(validObjectType, "xrpl::decreaseOwnerCountForObject : valid object sle type");
if (!validObjectType)
return; // LCOV_EXCL_LINE
SLE::ref sponsorSle = getLedgerEntryReserveSponsor(view, objectSle);
decreaseOwnerCount(view, accountSle, sponsorSle, count, j);
}
void
adjustLoanBrokerOwnerCount(
ApplyView& view,
SLE::ref brokerSle,
std::int32_t delta,
beast::Journal j)
{
XRPL_ASSERT(
brokerSle && brokerSle->getType() == ltLOAN_BROKER,
"xrpl::adjustLoanBrokerOwnerCount : valid loan broker sle");
if (!brokerSle || brokerSle->getType() != ltLOAN_BROKER)
return; // LCOV_EXCL_LINE
XRPL_ASSERT(delta != 0, "xrpl::adjustLoanBrokerOwnerCount : nonzero delta input");
if (delta == 0)
return; // LCOV_EXCL_LINE
adjustOwnerCountImpl(
view, brokerSle, sfOwnerCount, brokerSle->getAccountID(sfAccount), delta, j);
}
XRPAmount
accountReserve(ReadView const& view, SLE::const_ref sle, beast::Journal j, Adjustment adj)
{
XRPL_ASSERT(sle && sle->getType() == ltACCOUNT_ROOT, "xrpl::accountReserve : valid sle");
if (!view.rules().enabled(featureSponsor))
{
XRPL_ASSERT(adj.accountCountDelta == 0, "xrpl::accountReserve : no account count delta");
return view.fees().accountReserve(sle->getFieldU32(sfOwnerCount) + adj.ownerCountDelta, 1);
}
std::uint32_t const currentOwnerCount = ownerCount(sle, j, adj.ownerCountDelta);
std::uint32_t const currentAccountCount = accountCountImpl(sle, adj.accountCountDelta, j);
return view.fees().accountReserve(currentOwnerCount, currentAccountCount);
}
TER
checkReserve(
ApplyViewContext ctx,
SLE::const_ref accSle,
XRPAmount accBalance,
SLE::const_ref sponsorSle,
Adjustment adj,
beast::Journal j,
TER insufReserveCode)
{
// TODO: swap to assert after fixCleanup3_2_0 is retired
if (!accSle || accSle->getType() != ltACCOUNT_ROOT)
return tefINTERNAL; // LCOV_EXCL_LINE
XRPL_ASSERT(
!isTesSuccess(insufReserveCode), "xrpl::checkReserve : insufReserveCode is not tesSUCCESS");
if (ctx.view.rules().enabled(featureSponsor))
{
if (sponsorSle)
{
if (sponsorSle->getType() != ltACCOUNT_ROOT)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const sle = ctx.view.read(
keylet::sponsorship(
sponsorSle->getAccountID(sfAccount), accSle->getAccountID(sfAccount)));
// A reserve-sponsored tx must carry a sponsor signature
// (cosigning path) and/or have a pre-existing sponsorship SLE
// (prefunded path). Absence of both is an internal invariant break.
if (isReserveSponsored(ctx.tx) && !sle && !ctx.tx.isFieldPresent(sfSponsorSignature))
return tecINTERNAL; // LCOV_EXCL_LINE
if (sle)
{
auto const ownerCountAllowed = sle->getFieldU32(sfRemainingOwnerCount);
if (adj.ownerCountDelta > 0 &&
ownerCountAllowed < static_cast<std::uint32_t>(adj.ownerCountDelta))
return insufReserveCode;
}
auto const sponsorBalance = sponsorSle->getFieldAmount(sfBalance).xrp();
XRPAmount const sponsorReserve = accountReserve(ctx.view, sponsorSle, j, adj);
if (sponsorBalance < sponsorReserve)
return insufReserveCode;
}
else
{
XRPAmount const reserve = accountReserve(ctx.view, accSle, j, adj);
if (accBalance < reserve)
return insufReserveCode;
}
}
else
{
XRPL_ASSERT(
!sponsorSle,
"xrpl::checkReserve : featureSponsor disabled and sponsorSle not provided");
XRPL_ASSERT(adj.accountCountDelta == 0, "xrpl::checkReserve : accountCountDelta is 0");
auto const reserve = ctx.view.fees().accountReserve(
accSle->getFieldU32(sfOwnerCount) + adj.ownerCountDelta, 1);
if (accBalance < reserve)
return insufReserveCode;
}
return tesSUCCESS;
}
TER
checkReserve(
ApplyViewContext ctx,
SLE::const_ref accSle,
XRPAmount accBalance,
Adjustment adj,
beast::Journal j)
{
auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, accSle);
if (!sponsorExp)
return sponsorExp.error(); // LCOV_EXCL_LINE
return checkReserve(ctx, accSle, accBalance, *sponsorExp, adj, j);
}
// ----------------------------------------------------
AccountID
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey)
{
@@ -97,7 +97,7 @@ deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j)
}
if (isOwner)
adjustOwnerCount(view, sleAccount, -1, j);
decreaseOwnerCountForObject(view, sleAccount, sleCredential, 1, j);
return tesSUCCESS;
};
+53 -38
View File
@@ -3,7 +3,6 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
@@ -11,6 +10,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
@@ -125,29 +125,29 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
ApplyViewContext ctx,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
beast::Journal journal)
{
auto const& mptID = mptIssue.getMptID();
auto const mpt = view.peek(keylet::mptokenIssuance(mptID));
auto const mpt = ctx.view.peek(keylet::mptokenIssuance(mptID));
if (!mpt)
return tefINTERNAL; // LCOV_EXCL_LINE
if (mpt->isFlag(lsfMPTLocked))
return tefINTERNAL; // LCOV_EXCL_LINE
if (view.peek(keylet::mptoken(mptID, accountID)))
if (ctx.view.peek(keylet::mptoken(mptID, accountID)))
return tecDUPLICATE;
if (accountID == mptIssue.getIssuer())
return tesSUCCESS;
return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
return authorizeMPToken(ctx, priorBalance, mptID, accountID, journal);
}
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
ApplyViewContext ctx,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
@@ -155,7 +155,7 @@ authorizeMPToken(
std::uint32_t flags,
std::optional<AccountID> holderID)
{
auto const sleAcct = view.peek(keylet::account(account));
auto const sleAcct = ctx.view.peek(keylet::account(account));
if (!sleAcct)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -170,19 +170,19 @@ authorizeMPToken(
if ((flags & tfMPTUnauthorize) != 0u)
{
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto const sleMpt = view.peek(mptokenKey);
auto const sleMpt = ctx.view.peek(mptokenKey);
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0 ||
(view.rules().enabled(fixCleanup3_1_3) &&
(ctx.view.rules().enabled(fixCleanup3_1_3) &&
(*sleMpt)[~sfLockedAmount].valueOr(0) != 0))
return tecINTERNAL; // LCOV_EXCL_LINE
if (!view.dirRemove(
if (!ctx.view.dirRemove(
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleAcct, -1, journal);
decreaseOwnerCountForObject(ctx.view, sleAcct, sleMpt, 1, journal);
view.erase(sleMpt);
ctx.view.erase(sleMpt);
return tesSUCCESS;
}
@@ -190,47 +190,57 @@ authorizeMPToken(
// - add the new mptokenKey to the owner directory
// - create the MPToken object for the holder
// A reserve sponsor only covers tx.Account's own objects.
auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, sleAcct);
if (!sponsorExp)
return sponsorExp.error(); // LCOV_EXCL_LINE
auto const sponsorSle = *sponsorExp;
// The reserve that is required to create the MPToken. Note
// that although the reserve increases with every item
// an account owns, in the case of MPTokens we only
// *enforce* a reserve if the user owns more than two
// items. This is similar to the reserve requirements of trust lines.
std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
XRPAmount const reserveCreate(
(uOwnerCount < 2) ? XRPAmount(beast::kZero)
: view.fees().accountReserve(uOwnerCount + 1));
if (priorBalance < reserveCreate)
return tecINSUFFICIENT_RESERVE;
// The "free-tier" shortcut (ownerCount < 2) does not apply once a sponsor is on
// the tx — the sponsor must always cover the reserve (via balance or prefunded
// budget), so this check always runs for sponsored transactions.
if (sponsorSle || ownerCount(sleAcct, journal) >= 2)
{
if (auto const ret = checkReserve(
ctx, sleAcct, priorBalance, sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return ret;
}
// Defensive check before we attempt to create MPToken for the issuer
auto const mpt = view.read(keylet::mptokenIssuance(mptIssuanceID));
auto const mpt = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
if (!mpt || mpt->getAccountID(sfIssuer) == account)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token");
if (view.rules().enabled(featureLendingProtocol))
if (ctx.view.rules().enabled(featureLendingProtocol))
return tecINTERNAL;
// LCOV_EXCL_STOP
}
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto mptoken = std::make_shared<SLE>(mptokenKey);
if (auto ter = dirLink(view, account, mptoken))
if (auto ter = dirLink(ctx.view, account, mptoken))
return ter; // LCOV_EXCL_LINE
(*mptoken)[sfAccount] = account;
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
(*mptoken)[sfFlags] = 0;
view.insert(mptoken);
ctx.view.insert(mptoken);
// Update owner count.
adjustOwnerCount(view, sleAcct, 1, journal);
increaseOwnerCount(ctx.view, sleAcct, sponsorSle, 1, journal);
addSponsorToLedgerEntry(mptoken, sponsorSle);
return tesSUCCESS;
}
auto const sleMptIssuance = view.read(keylet::mptokenIssuance(mptIssuanceID));
auto const sleMptIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
if (!sleMptIssuance)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -240,7 +250,7 @@ authorizeMPToken(
if (account != (*sleMptIssuance)[sfIssuer])
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
auto const sleMpt = ctx.view.peek(keylet::mptoken(mptIssuanceID, *holderID));
if (!sleMpt)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -263,13 +273,13 @@ authorizeMPToken(
if (flagsIn != flagsOut)
sleMpt->setFieldU32(sfFlags, flagsOut);
view.update(sleMpt);
ctx.view.update(sleMpt);
return tesSUCCESS;
}
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
ApplyViewContext ctx,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal)
@@ -279,7 +289,7 @@ removeEmptyHolding(
// a token does exist, it will get deleted. If not, return success.
bool const accountIsIssuer = accountID == mptIssue.getIssuer();
auto const& mptID = mptIssue.getMptID();
auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
auto const mptoken = ctx.view.peek(keylet::mptoken(mptID, accountID));
if (!mptoken)
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
// Unlike a trust line, if the account is the issuer, and the token has a
@@ -287,7 +297,7 @@ removeEmptyHolding(
// accounting out of balance, so fail. Since this should be impossible
// anyway, I'm not going to put any effort into it.
if (mptoken->at(sfMPTAmount) != 0 ||
(view.rules().enabled(fixCleanup3_1_3) && (*mptoken)[~sfLockedAmount].valueOr(0) != 0))
(ctx.view.rules().enabled(fixCleanup3_1_3) && (*mptoken)[~sfLockedAmount].valueOr(0) != 0))
return tecHAS_OBLIGATIONS;
// Don't delete if the token still has confidential balances
@@ -300,7 +310,7 @@ removeEmptyHolding(
}
return authorizeMPToken(
view,
ctx,
{}, // priorBalance
mptID,
accountID,
@@ -419,13 +429,13 @@ requireAuth(
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
ApplyViewContext ctx,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance, // for MPToken authorization
beast::Journal j)
{
auto const sleIssuance = view.read(keylet::mptokenIssuance(mptIssuanceID));
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
if (!sleIssuance)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -437,7 +447,7 @@ enforceMPTokenAuthorization(
return tefINTERNAL; // LCOV_EXCL_LINE
auto const keylet = keylet::mptoken(mptIssuanceID, account);
auto const sleToken = view.read(keylet); // NOTE: might be null
auto const sleToken = ctx.view.read(keylet); // NOTE: might be null
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
bool expired = false;
bool const authorizedByDomain = [&]() -> bool {
@@ -445,7 +455,7 @@ enforceMPTokenAuthorization(
if (!maybeDomainID.has_value())
return false; // LCOV_EXCL_LINE
auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
auto const ter = verifyValidDomain(ctx.view, account, *maybeDomainID, j);
if (isTesSuccess(ter))
return true;
if (ter == tecEXPIRED)
@@ -500,7 +510,7 @@ enforceMPTokenAuthorization(
maybeDomainID.has_value() && sleToken == nullptr,
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
if (auto const err = authorizeMPToken(
view,
ctx,
priorBalance, // priorBalance
mptIssuanceID, // mptIssuanceID
account, // account
@@ -915,6 +925,7 @@ createMPToken(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
SLE::ref sponsorSle,
std::uint32_t const flags)
{
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
@@ -931,6 +942,8 @@ createMPToken(
(*mptoken)[sfFlags] = flags;
(*mptoken)[sfOwnerNode] = *ownerNode;
addSponsorToLedgerEntry(mptoken, sponsorSle);
view.insert(mptoken);
return tesSUCCESS;
@@ -941,6 +954,7 @@ checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
SLE::ref sponsorSle,
beast::Journal j)
{
if (mptIssue.getIssuer() == holder)
@@ -950,7 +964,7 @@ checkCreateMPT(
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
if (!view.exists(mptokenID))
{
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0);
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, sponsorSle, 0);
!isTesSuccess(err))
{
return err;
@@ -960,7 +974,8 @@ checkCreateMPT(
{
return tecINTERNAL;
}
adjustOwnerCount(view, sleAcct, 1, j);
increaseOwnerCount(view, sleAcct, sponsorSle, 1, j);
}
return tesSUCCESS;
}
+14 -29
View File
@@ -269,11 +269,7 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft)
// the NFT.
SLE::pointer const page =
getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) {
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
1,
beast::Journal{beast::Journal::getNullSink()});
increaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()});
});
if (!page)
@@ -411,21 +407,17 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
curr->setFieldArray(sfNFTokens, arr);
view.update(curr);
int cnt = 0;
std::uint32_t cnt = 0;
if (prev && mergePages(view, prev, curr))
cnt--;
++cnt;
if (next && mergePages(view, curr, next))
cnt--;
++cnt;
if (cnt != 0)
{
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
cnt,
beast::Journal{beast::Journal::getNullSink()});
decreaseOwnerCount(view, owner, {}, cnt, beast::Journal{beast::Journal::getNullSink()});
}
return tesSUCCESS;
@@ -460,11 +452,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
curr->makeFieldAbsent(sfPreviousPageMin);
}
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
-1,
beast::Journal{beast::Journal::getNullSink()});
decreaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()});
view.update(curr);
view.erase(prev);
@@ -502,7 +490,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
view.erase(curr);
int cnt = 1;
uint32_t cnt = 1;
// Since we're here, try to consolidate the previous and current pages
// of the page we removed (if any) into one. mergePages() _should_
@@ -519,11 +507,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
view.peek(Keylet(ltNFTOKEN_PAGE, next->key()))))
cnt++;
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
-1 * cnt,
beast::Journal{beast::Journal::getNullSink()});
decreaseOwnerCount(view, owner, {}, cnt, beast::Journal{beast::Journal::getNullSink()});
return tesSUCCESS;
}
@@ -639,8 +623,7 @@ deleteTokenOffer(ApplyView& view, SLE::ref offer)
false))
return false;
adjustOwnerCount(
view, view.peek(keylet::account(owner)), -1, beast::Journal{beast::Journal::getNullSink()});
decreaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()});
view.erase(offer);
return true;
@@ -743,9 +726,11 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner)
auto const newPrev = view.peek(Keylet(ltNFTOKEN_PAGE, *prevLink));
if (!newPrev)
{
// LCOV_EXCL_START
Throw<std::runtime_error>(
"NFTokenPage directory for " + to_string(owner) +
" cannot be repaired. Unexpected link problem.");
" cannot be repaired. Unexpected link problem.");
// LCOV_EXCL_STOP
}
newPrev->at(sfNextPageMin) = nextPage->key();
view.update(newPrev);
@@ -931,7 +916,7 @@ tokenOfferCreateApply(
{
Keylet const acctKeylet = keylet::account(acctID);
if (auto const acct = view.read(acctKeylet);
priorBalance < view.fees().accountReserve((*acct)[sfOwnerCount] + 1))
priorBalance < accountReserve(view, acct, j, {.ownerCountDelta = 1}))
return tecINSUFFICIENT_RESERVE;
auto const offerID = keylet::nftokenOffer(acctID, seqProxy.value());
@@ -983,7 +968,7 @@ tokenOfferCreateApply(
}
// Update owner count.
adjustOwnerCount(view, view.peek(acctKeylet), 1, j);
increaseOwnerCount(view, acctID, {}, 1, j);
return tesSUCCESS;
}
+1 -1
View File
@@ -55,7 +55,7 @@ offerDelete(ApplyView& view, SLE::ref sle, beast::Journal j)
}
}
adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
decreaseOwnerCountForObject(view, owner, sle, 1, j);
view.erase(sle);
@@ -58,7 +58,7 @@ closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal
XRPL_ASSERT(
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
adjustOwnerCount(view, sle, -1, j);
decreaseOwnerCountForObject(view, sle, slep, 1, j);
view.update(sle);
// Remove PayChan from ledger
@@ -9,6 +9,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/AmountConversions.h>
@@ -195,6 +196,7 @@ trustCreate(
// Issuer should be the account being set.
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
SLE::ref sponsorSle,
beast::Journal j)
{
JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
@@ -281,7 +283,9 @@ trustCreate(
}
sleRippleState->setFieldU32(sfFlags, uFlags);
adjustOwnerCount(view, sleAccount, 1, j);
increaseOwnerCount(view, sleAccount, sponsorSle, 1, j);
addSponsorToLedgerEntry(sleRippleState, sponsorSle, bSetHigh ? sfHighSponsor : sfLowSponsor);
// ONLY: Create ripple balance.
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
@@ -317,6 +321,9 @@ trustDelete(
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
removeSponsorFromLedgerEntry(sleRippleState, sfHighSponsor);
removeSponsorFromLedgerEntry(sleRippleState, sfLowSponsor);
JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
view.erase(sleRippleState);
@@ -369,11 +376,15 @@ updateTrustLine(
{
// VFALCO Where is the line being deleted?
// Clear the reserve of the sender, possibly delete the line!
adjustOwnerCount(view, sle, -1, j);
auto const currentSponsor =
getLedgerEntryReserveSponsor(view, state, bSenderHigh ? sfHighSponsor : sfLowSponsor);
decreaseOwnerCount(view, sle, currentSponsor, 1, j);
// Clear reserve flag.
state->clearFlag(senderReserveFlag);
removeSponsorFromLedgerEntry(state, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
// Balance is zero, receiver reserve is clear.
if (!after && !state->isFlag(receiverReserveFlag))
return true;
@@ -381,12 +392,14 @@ updateTrustLine(
return false;
}
// Only used in tests
TER
issueIOU(
ApplyView& view,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
SLE::ref sponsorSle,
beast::Journal j)
{
XRPL_ASSERT(
@@ -472,6 +485,7 @@ issueIOU(
limit,
0,
0,
sponsorSle,
j);
}
@@ -620,7 +634,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
TER
addEmptyHolding(
ApplyView& view,
ApplyViewContext ctx,
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,
@@ -632,30 +646,45 @@ addEmptyHolding(
auto const& issuerId = issue.getIssuer();
auto const& currency = issue.currency;
if (isGlobalFrozen(view, issuerId))
if (isGlobalFrozen(ctx.view, issuerId))
return tecFROZEN; // LCOV_EXCL_LINE
auto const& srcId = issuerId;
auto const& dstId = accountID;
auto const high = srcId > dstId;
auto const index = keylet::trustLine(srcId, dstId, currency);
auto const sleSrc = view.peek(keylet::account(srcId));
auto const sleDst = view.peek(keylet::account(dstId));
auto const sleSrc = ctx.view.peek(keylet::account(srcId));
auto const sleDst = ctx.view.peek(keylet::account(dstId));
if (!sleDst || !sleSrc)
return tefINTERNAL; // LCOV_EXCL_LINE
if (!sleSrc->isFlag(lsfDefaultRipple))
return tecINTERNAL; // LCOV_EXCL_LINE
// If the line already exists, don't create it again.
if (view.read(index))
if (ctx.view.read(index))
return tecDUPLICATE;
// A reserve sponsor only covers tx.Account's own objects.
auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, sleDst);
if (!sponsorExp)
return sponsorExp.error(); // LCOV_EXCL_LINE
auto const sponsorSle = *sponsorExp;
// Can the account cover the trust line reserve ?
std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
if (priorBalance < view.fees().accountReserve(ownerCount + 1))
return tecNO_LINE_INSUF_RESERVE;
if (auto const ret = checkReserve(
ctx,
sleDst,
priorBalance,
sponsorSle,
{.ownerCountDelta = 1},
journal,
tecNO_LINE_INSUF_RESERVE);
!isTesSuccess(ret))
{
return ret;
}
return trustCreate(
view,
ctx.view,
high,
srcId,
dstId,
@@ -669,19 +698,20 @@ addEmptyHolding(
/*saLimit=*/STAmount{Issue{currency, dstId}},
/*uQualityIn=*/0,
/*uQualityOut=*/0,
sponsorSle,
journal);
}
TER
removeEmptyHolding(
ApplyView& view,
ApplyViewContext ctx,
AccountID const& accountID,
Issue const& issue,
beast::Journal journal)
{
if (issue.native())
{
auto const sle = view.read(keylet::account(accountID));
auto const sle = ctx.view.read(keylet::account(accountID));
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -696,7 +726,7 @@ removeEmptyHolding(
// If the account is the issuer, then no line should exist. Check anyway.
// If a line does exist, it will get deleted. If not, return success.
bool const accountIsIssuer = accountID == issue.account;
auto const line = view.peek(keylet::trustLine(accountID, issue));
auto const line = ctx.view.peek(keylet::trustLine(accountID, issue));
if (!line)
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::kZero)
@@ -706,33 +736,43 @@ removeEmptyHolding(
if (line->isFlag(lsfLowReserve))
{
// Clear reserve for low account.
auto sleLowAccount = view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
auto sleLowAccount = ctx.view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
if (!sleLowAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleLowAccount, -1, journal);
auto const currentLowSponsor = getLedgerEntryReserveSponsor(ctx.view, line, sfLowSponsor);
decreaseOwnerCount(ctx.view, sleLowAccount, currentLowSponsor, 1, journal);
// It's not really necessary to clear the reserve flag, since the line
// is about to be deleted, but this will make the metadata reflect an
// accurate state at the time of deletion.
line->clearFlag(lsfLowReserve);
removeSponsorFromLedgerEntry(line, sfLowSponsor);
}
if (line->isFlag(lsfHighReserve))
{
// Clear reserve for high account.
auto sleHighAccount = view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
auto sleHighAccount = ctx.view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
if (!sleHighAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleHighAccount, -1, journal);
auto const currentHighSponsor = getLedgerEntryReserveSponsor(ctx.view, line, sfHighSponsor);
decreaseOwnerCount(ctx.view, sleHighAccount, currentHighSponsor, 1, journal);
// It's not really necessary to clear the reserve flag, since the line
// is about to be deleted, but this will make the metadata reflect an
// accurate state at the time of deletion.
line->clearFlag(lsfHighReserve);
removeSponsorFromLedgerEntry(line, sfHighSponsor);
}
return trustDelete(
view, line, line->at(sfLowLimit)->getIssuer(), line->at(sfHighLimit)->getIssuer(), journal);
ctx.view,
line,
line->at(sfLowLimit)->getIssuer(),
line->at(sfHighLimit)->getIssuer(),
journal);
}
TER
@@ -768,6 +808,9 @@ deleteAMMTrustLine(
if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
return terNO_AMM;
auto const sponsorSle =
getLedgerEntryReserveSponsor(view, sleState, !ammLow ? sfLowSponsor : sfHighSponsor);
if (auto const ter = trustDelete(view, sleState, low, high, j); !isTesSuccess(ter))
{
JLOG(j.error()) << "deleteAMMTrustLine: failed to delete the trustline.";
@@ -778,7 +821,7 @@ deleteAMMTrustLine(
if (!sleState->isFlag(uFlags))
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
decreaseOwnerCount(view, !ammLow ? sleLow : sleHigh, sponsorSle, 1, j);
return tesSUCCESS;
}
@@ -0,0 +1,345 @@
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/OracleHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFormats.h>
#include <cstdint>
#include <expected>
#include <optional>
#include <unordered_set>
namespace xrpl {
bool
isReserveSponsorAllowed(TxType txType)
{
// Transaction types explicitly allow-listed for reserve sponsorship, for
// v1. Lazily-initialized function-local static: constructed once on first
// use, with no startup cost paid by clients that never call this.
static std::unordered_set<TxType> const kReserveSponsorAllowed = {
ttDELEGATE_SET,
ttDEPOSIT_PREAUTH,
ttPAYMENT,
ttSIGNER_LIST_SET,
ttCHECK_CANCEL,
ttCHECK_CASH,
ttCHECK_CREATE,
ttESCROW_CANCEL,
ttESCROW_CREATE,
ttESCROW_FINISH,
ttPAYCHAN_CLAIM,
ttPAYCHAN_CREATE,
ttPAYCHAN_FUND,
ttCLAWBACK,
ttMPTOKEN_AUTHORIZE,
ttMPTOKEN_ISSUANCE_CREATE,
ttMPTOKEN_ISSUANCE_DESTROY,
ttMPTOKEN_ISSUANCE_SET,
ttTRUST_SET,
ttCREDENTIAL_ACCEPT,
ttCREDENTIAL_CREATE,
ttCREDENTIAL_DELETE,
ttACCOUNT_SET,
ttREGULAR_KEY_SET,
ttSPONSORSHIP_TRANSFER,
};
return kReserveSponsorAllowed.contains(txType);
}
std::optional<AccountID>
getTxReserveSponsorID(STTx const& tx)
{
if (tx.isFieldPresent(sfSponsor) && isReserveSponsored(tx))
{
XRPL_ASSERT(
getCurrentTransactionRules()->enabled( // NOLINT(bugprone-unchecked-optional-access)
featureSponsor),
"xrpl::getTxReserveSponsorID : sponsor exists + Sponsor enabled");
return tx.getAccountID(sfSponsor);
}
return {};
}
std::expected<SLE::pointer, TER>
getTxReserveSponsor(ApplyViewContext ctx)
{
auto const sponsorID = getTxReserveSponsorID(ctx.tx);
if (sponsorID)
{
XRPL_ASSERT(
ctx.view.rules().enabled(featureSponsor),
"xrpl::getTxReserveSponsor : sponsor exists + Sponsor enabled");
auto sle = ctx.view.peek(keylet::account(*sponsorID));
// already checked in Transactor::checkSponsor
if (!sle)
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
return sle;
}
return SLE::pointer();
}
std::expected<SLE::const_pointer, TER>
getTxReserveSponsor(ReadView const& view, STTx const& tx)
{
auto const sponsorID = getTxReserveSponsorID(tx);
if (sponsorID)
{
XRPL_ASSERT(
view.rules().enabled(featureSponsor),
"xrpl::getTxReserveSponsor : sponsor exists + Sponsor enabled");
auto sle = view.read(keylet::account(*sponsorID));
// already checked in Transactor::checkSponsor
if (!sle)
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
return sle;
}
return SLE::pointer();
}
std::expected<SLE::pointer, TER>
getEffectiveTxReserveSponsor(ApplyViewContext ctx, SLE::const_ref accountSle)
{
// A reserve sponsor only covers tx.Account's own objects.
if (ctx.view.rules().enabled(fixCleanup3_2_0))
{
XRPL_ASSERT(
accountSle && accountSle->getType() == ltACCOUNT_ROOT,
"xrpl::getEffectiveTxReserveSponsor : accountSle exists and is account type");
}
else
{
XRPL_ASSERT(
accountSle &&
((accountSle->getType() == ltACCOUNT_ROOT) || (accountSle->getType() == ltESCROW)),
"xrpl::getEffectiveTxReserveSponsor : accountSle exists and is account type");
}
if (isPseudoAccount(accountSle) || accountSle->getAccountID(sfAccount) != ctx.tx[sfAccount])
return SLE::pointer();
return getTxReserveSponsor(ctx);
}
std::optional<AccountID>
getLedgerEntryReserveSponsorID(SLE::const_ref sle, SF_ACCOUNT const& field)
{
XRPL_ASSERT(
(sle &&
((sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) ||
(sle->getType() != ltRIPPLE_STATE && field == sfSponsor))),
"xrpl::getLedgerEntryReserveSponsorID : correct sfield");
if (sle->isFieldPresent(field))
return sle->getAccountID(field);
return {};
}
SLE::pointer
getLedgerEntryReserveSponsor(ApplyView& view, SLE::const_ref sle, SF_ACCOUNT const& field)
{
auto const sponsorID = getLedgerEntryReserveSponsorID(sle, field);
if (sponsorID)
{
XRPL_ASSERT(
view.rules().enabled(featureSponsor),
"xrpl::getLedgerEntryReserveSponsor : sponsor exists + Sponsor enabled");
return view.peek(keylet::account(*sponsorID));
}
return {};
}
void
addSponsorToLedgerEntry(SLE::ref sle, SLE::const_ref sponsorSle, SF_ACCOUNT const& field)
{
XRPL_ASSERT(
(sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) ||
(sle->getType() != ltRIPPLE_STATE && field == sfSponsor),
"addSponsorToLedgerEntry : Invalid field to the LedgerEntry");
if (sponsorSle)
{
XRPL_ASSERT(
getCurrentTransactionRules()->enabled( // NOLINT(bugprone-unchecked-optional-access)
featureSponsor),
"xrpl::addSponsorToLedgerEntry : sponsor exists + Sponsor enabled");
sle->setAccountID(field, sponsorSle->getAccountID(sfAccount));
}
}
void
addSponsorToLedgerEntry(ApplyViewContext ctx, SLE::ref sle, SF_ACCOUNT const& field)
{
// getTxReserveSponsor yields a null pointer when the tx is not
// reserve-sponsored, so addSponsorToLedgerEntry becomes a no-op then. The
// error case (tecINTERNAL) is an already-checked invariant; skip stamping.
auto const sponsorSle = getTxReserveSponsor(ctx);
if (sponsorSle && *sponsorSle)
{
XRPL_ASSERT(
ctx.view.rules().enabled(featureSponsor),
"xrpl::addSponsorToLedgerEntry : sponsor exists + Sponsor enabled");
addSponsorToLedgerEntry(sle, *sponsorSle, field);
}
}
void
removeSponsorFromLedgerEntry(SLE::ref sle, SF_ACCOUNT const& field)
{
XRPL_ASSERT(
(sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) ||
(sle->getType() != ltRIPPLE_STATE && field == sfSponsor),
"removeSponsorFromLedgerEntry : Invalid field to the LedgerEntry");
if (sle->isFieldPresent(field))
{
XRPL_ASSERT(
getCurrentTransactionRules()->enabled( // NOLINT(bugprone-unchecked-optional-access)
featureSponsor),
"xrpl::removeSponsorFromLedgerEntry : sponsor exists + Sponsor enabled");
sle->makeFieldAbsent(field);
}
}
bool
isLedgerEntryOwner(ReadView const& view, SLE const& sle, AccountID const& account)
{
switch (sle.getType())
{
case ltCHECK:
case ltESCROW:
case ltPAYCHAN:
case ltMPTOKEN:
case ltDELEGATE:
case ltDEPOSIT_PREAUTH:
return sle.getAccountID(sfAccount) == account;
case ltMPTOKEN_ISSUANCE:
return sle.getAccountID(sfIssuer) == account;
case ltSIGNER_LIST: {
auto const signerList = view.read(keylet::signerList(account));
if (!signerList)
return false;
return signerList->key() == sle.key();
}
case ltCREDENTIAL: {
auto const& ownerField = sle.isFlag(lsfAccepted) ? sfSubject : sfIssuer;
return sle.getAccountID(ownerField) == account;
}
case ltRIPPLE_STATE: {
if (sle.isFlag(lsfHighReserve))
{
auto const highAccount = sle.getFieldAmount(sfHighLimit).getIssuer();
if (highAccount == account)
return true;
}
if (sle.isFlag(lsfLowReserve))
{
auto const lowAccount = sle.getFieldAmount(sfLowLimit).getIssuer();
if (lowAccount == account)
return true;
}
// Reachable: the sponsee may be a third party or the side of the
// line that holds no reserve (e.g. the issuer). Callers map this
// to tecNO_PERMISSION.
return false;
}
default:
// LCOV_EXCL_START
UNREACHABLE("xrpl::isLedgerEntryOwner : object is not supported by sponsorship.");
return false;
// LCOV_EXCL_STOP
};
}
bool
isLedgerEntrySupportedBySponsorship(SLE const& sle)
{
switch (sle.getType())
{
case ltCHECK:
case ltESCROW:
case ltPAYCHAN:
case ltMPTOKEN:
case ltDELEGATE:
case ltDEPOSIT_PREAUTH:
case ltMPTOKEN_ISSUANCE:
case ltSIGNER_LIST:
case ltCREDENTIAL:
case ltRIPPLE_STATE:
return true;
default:
return false;
};
}
std::uint32_t
getLedgerEntryOwnerCount(SLE const& sle)
{
switch (sle.getType())
{
case ltORACLE: {
return calculateOracleReserve(sle.getFieldArray(sfPriceDataSeries));
}
// Vaults require 2 owner counts (the vault and a pseudo-account)
case ltVAULT:
return 2;
case ltSIGNER_LIST: {
// Mirror SignerListSet's owner-count accounting so that create and
// delete agree. Modern lists (post-MultiSignReserve) carry the
// lsfOneOwnerCount flag and cost a single owner count. Legacy
// pre-MultiSignReserve lists cost 2 + signer_count owner counts
if (sle.isFlag(lsfOneOwnerCount))
return 1;
return 2 + static_cast<std::uint32_t>(sle.getFieldArray(sfSignerEntries).size());
}
case ltACCOUNT_ROOT:
// LCOV_EXCL_START
UNREACHABLE("AccountRoots are not supported by object sponsorship.");
return 0;
// LCOV_EXCL_STOP
default:
return 1;
}
}
SF_ACCOUNT const&
getLedgerEntrySponsorField(SLE const& sle, AccountID const& owner)
{
switch (sle.getType())
{
case ltRIPPLE_STATE: {
if (sle.isFlag(lsfHighReserve))
{
auto const highAccount = sle.getFieldAmount(sfHighLimit).getIssuer();
if (highAccount == owner)
return sfHighSponsor;
}
if (sle.isFlag(lsfLowReserve))
{
auto const lowAccount = sle.getFieldAmount(sfLowLimit).getIssuer();
if (lowAccount == owner)
return sfLowSponsor;
}
// LCOV_EXCL_START
UNREACHABLE("xrpl::getLedgerEntrySponsorField : unknown owner for RippleState");
return sfSponsor;
// LCOV_EXCL_STOP
}
default:
return sfSponsor;
}
}
} // namespace xrpl
+30 -14
View File
@@ -10,6 +10,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Concepts.h>
@@ -571,7 +572,7 @@ canAddHolding(ReadView const& view, Asset const& asset)
TER
addEmptyHolding(
ApplyView& view,
ApplyViewContext ctx,
AccountID const& accountID,
XRPAmount priorBalance,
Asset const& asset,
@@ -579,21 +580,21 @@ addEmptyHolding(
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return addEmptyHolding(view, accountID, priorBalance, issue, journal);
return addEmptyHolding(ctx, accountID, priorBalance, issue, journal);
},
asset.value());
}
TER
removeEmptyHolding(
ApplyView& view,
ApplyViewContext ctx,
AccountID const& accountID,
Asset const& asset,
beast::Journal journal)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return removeEmptyHolding(view, accountID, issue, journal);
return removeEmptyHolding(ctx, accountID, issue, journal);
},
asset.value());
}
@@ -647,6 +648,7 @@ directSendNoFeeIOU(
AccountID const& uReceiverID,
STAmount const& saAmount,
bool bCheckIssuer,
SLE::ref sponsorSle,
beast::Journal j)
{
AccountID const& issuer = saAmount.getIssuer();
@@ -717,7 +719,12 @@ directSendNoFeeIOU(
// Sender quality out is 0.
{
// Clear the reserve of the sender, possibly delete the line!
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), -1, j);
auto const currentSponsor = getLedgerEntryReserveSponsor(
view, sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
decreaseOwnerCount(view, view.peek(keylet::account(uSenderID)), currentSponsor, 1, j);
removeSponsorFromLedgerEntry(
sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
// Clear reserve flag.
sleRippleState->clearFlag(senderReserveFlag);
@@ -780,6 +787,7 @@ directSendNoFeeIOU(
saReceiverLimit,
0,
0,
sponsorSle,
j);
}
@@ -794,6 +802,7 @@ directSendNoLimitIOU(
STAmount const& saAmount,
STAmount& saActual,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee)
{
auto const& issuer = saAmount.getIssuer();
@@ -806,7 +815,8 @@ directSendNoLimitIOU(
if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
{
// Direct send: redeeming IOUs and/or sending own IOUs.
auto const ter = directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, j);
auto const ter =
directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, sponsorSle, j);
if (!isTesSuccess(ter))
return ter;
saActual = saAmount;
@@ -824,10 +834,12 @@ directSendNoLimitIOU(
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
<< " cost=" << saActual.getFullText();
TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, j);
TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, sponsorSle, j);
if (tesSUCCESS == terResult)
terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, j);
{
terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, sponsorSle, j);
}
return terResult;
}
@@ -870,7 +882,8 @@ directSendNoLimitMultiIOU(
if (senderID == issuer || receiverID == issuer || issuer == noAccount())
{
// Direct send: redeeming IOUs and/or sending own IOUs.
if (auto const ter = directSendNoFeeIOU(view, senderID, receiverID, amount, false, j);
if (auto const ter =
directSendNoFeeIOU(view, senderID, receiverID, amount, false, {}, j);
!isTesSuccess(ter))
return ter;
actual += amount;
@@ -894,14 +907,14 @@ directSendNoLimitMultiIOU(
<< to_string(receiverID) << " : deliver=" << amount.getFullText()
<< " cost=" << actual.getFullText();
if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, j))
if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, {}, j))
return terResult;
}
if (senderID != issuer && takeFromSender)
{
if (TER const terResult =
directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, j))
directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, {}, j))
return terResult;
}
@@ -915,6 +928,7 @@ accountSendIOU(
AccountID const& uReceiverID,
STAmount const& saAmount,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee)
{
if (view.rules().enabled(fixAMMv1_1))
@@ -946,7 +960,8 @@ accountSendIOU(
JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
<< to_string(uReceiverID) << " : " << saAmount.getFullText();
return directSendNoLimitIOU(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
return directSendNoLimitIOU(
view, uSenderID, uReceiverID, saAmount, saActual, j, sponsorSle, waiveFee);
}
/* XRP send which does not check reserve and can do pure adjustment.
@@ -1475,7 +1490,7 @@ directSendNoFee(
{
return saAmount.asset().visit(
[&](Issue const&) {
return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, {}, j);
},
[&](MPTIssue const&) {
XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer");
@@ -1490,12 +1505,13 @@ accountSend(
AccountID const& uReceiverID,
STAmount const& saAmount,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee,
AllowMPTOverflow allowOverflow)
{
return saAmount.asset().visit(
[&](Issue const&) {
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee);
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, sponsorSle, waiveFee);
},
[&](MPTIssue const&) {
return accountSendMPT(
+7
View File
@@ -84,6 +84,7 @@ enum class LedgerNameSpace : std::uint16_t {
Vault = 'V',
LoanBroker = 'l', // lower-case L
Loan = 'L',
Sponsorship = '>',
// No longer used or supported. Left here to reserve the space to avoid accidental reuse.
Contract [[deprecated]] = 'c',
@@ -326,6 +327,12 @@ signerList(AccountID const& account) noexcept
return signerList(account, 0);
}
Keylet
sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept
{
return {ltSPONSORSHIP, indexHash(LedgerNameSpace::Sponsorship, sponsor, sponsee)};
}
Keylet
check(AccountID const& id, std::uint32_t seq) noexcept
{
@@ -160,6 +160,14 @@ InnerObjectFormats::InnerObjectFormats()
{sfTxnSignature, SoeOptional},
{sfSigners, SoeOptional},
});
add(sfSponsorSignature.jsonName.cStr(),
sfSponsorSignature.getCode(),
{
{sfSigningPubKey, SoeOptional},
{sfTxnSignature, SoeOptional},
{sfSigners, SoeOptional},
});
}
InnerObjectFormats const&
+1
View File
@@ -15,6 +15,7 @@ LedgerFormats::getCommonFields()
{sfLedgerIndex, SoeOptional},
{sfLedgerEntryType, SoeRequired},
{sfFlags, SoeRequired},
{sfSponsor, SoeOptional},
};
return kCommonFields;
}
+3 -3
View File
@@ -634,16 +634,16 @@ STObject::getAccountID(SField const& field) const
}
AccountID
STObject::getFeePayer() const
STObject::getInitiator() const
{
// If sfDelegate is present, the delegate account is the payer
// If sfDelegate is present, the delegate account is the initiator
// note: if a delegate is specified, its authorization to act on behalf of the account is
// enforced in `Transactor::invokeCheckPermission`
// cryptographic signature validity is checked separately (e.g., in `Transactor::checkSign`)
if (isFieldPresent(sfDelegate))
return getAccountID(sfDelegate);
// Default payer
// Default initiator
return getAccountID(sfAccount);
}
+18 -1
View File
@@ -28,6 +28,7 @@
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/Sign.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/jss.h>
@@ -271,6 +272,13 @@ STTx::checkSign(Rules const& rules) const
return std::unexpected("Counterparty: " + ret.error());
}
if (isFieldPresent(sfSponsorSignature))
{
auto const sponsorSignatureObj = getFieldObject(sfSponsorSignature);
if (auto const ret = checkSign(rules, sponsorSignatureObj); !ret)
return std::unexpected("Sponsor: " + ret.error());
}
// Verify the batch signer signatures here too, so they are cached with the
// rest of signature checking (checkValidity / SF_SIGGOOD) and stay out of
// the transaction engine. Gated on a batch (batchTxnIds_ seated) that
@@ -551,7 +559,7 @@ STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const
// For delegated transactions sfDelegate is the account whose signer list is checked,
// the delegate account itself can not be among the signers.
auto const txnAccountID =
&sigObject != this ? std::nullopt : std::optional<AccountID>(getFeePayer());
&sigObject != this ? std::nullopt : std::optional<AccountID>(getInitiator());
// We can ease the computational load inside the loop a bit by
// pre-constructing part of the data that we hash. Fill a Serializer
@@ -603,6 +611,15 @@ STTx::getBatchTransactionIDs() const
return *batchTxnIds_;
}
AccountID
STTx::getFeePayerID() const
{
if (isFieldPresent(sfSponsor) && ((getFieldU32(sfSponsorFlags) & spfSponsorFee) != 0u))
return at(sfSponsor);
return getInitiator();
}
//------------------------------------------------------------------------------
static bool
+2
View File
@@ -107,6 +107,7 @@ transResults()
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
MAKE_ERROR(tecBAD_PROOF, "Proof cannot be verified"),
MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Sponsor has not authorized this transaction."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
@@ -220,6 +221,7 @@ transResults()
MAKE_ERROR(terADDRESS_COLLISION, "Failed to allocate an unique account address."),
MAKE_ERROR(terNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
MAKE_ERROR(terLOCKED, "Fund is locked."),
MAKE_ERROR(terNO_PERMISSION, "No permission to perform requested operation."),
MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."),
};
+3
View File
@@ -30,6 +30,9 @@ TxFormats::getCommonFields()
{sfSigners, SoeOptional}, // submit_multisigned
{sfNetworkID, SoeOptional},
{sfDelegate, SoeOptional},
{sfSponsor, SoeOptional},
{sfSponsorFlags, SoeOptional},
{sfSponsorSignature, SoeOptional},
};
return kCommonFields;
}
+315 -22
View File
@@ -17,6 +17,7 @@
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -41,6 +42,7 @@
#include <xrpl/tx/apply.h>
#include <xrpl/tx/applySteps.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <exception>
@@ -167,6 +169,58 @@ preflightCheckSimulateKeys(ApplyFlags flags, STObject const& sigObject, beast::J
} // namespace detail
static NotTEC
preflight1Sponsor(PreflightContext const& ctx)
{
bool const hasSponsor = ctx.tx.isFieldPresent(sfSponsor);
bool const hasSponsorFlags = ctx.tx.isFieldPresent(sfSponsorFlags);
bool const hasSponsorSig = ctx.tx.isFieldPresent(sfSponsorSignature);
if ((hasSponsor || hasSponsorFlags || hasSponsorSig) && !ctx.rules.enabled(featureSponsor))
return temDISABLED;
if (hasSponsor != hasSponsorFlags)
{
JLOG(ctx.j.debug()) << "preflight1: sponsor and sponsor flags mismatch";
return temINVALID_FLAG;
}
if (hasSponsorSig && (!hasSponsor || !hasSponsorFlags))
{
JLOG(ctx.j.debug()) << "preflight1: sponsor signature without sponsor definition";
return temMALFORMED;
}
if (hasSponsorFlags)
{
auto const sponsorFlags = ctx.tx.getFieldU32(sfSponsorFlags);
if (((sponsorFlags & spfSponsorFlagMask) != 0u) || sponsorFlags == 0)
{
JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags";
return temINVALID_FLAG;
}
// Reserve sponsorship is only permitted for an explicit allow-list of
// transaction types, for v1. All other tx types reject spfSponsorReserve here.
if (isReserveSponsored(ctx.tx))
{
if (!isReserveSponsorAllowed(ctx.tx.getTxnType()))
{
JLOG(ctx.j.debug())
<< "preflight1: spfSponsorReserve not allowed for this transaction type";
return temINVALID_FLAG;
}
}
}
if (hasSponsor && ctx.tx.getAccountID(sfSponsor) == ctx.tx.getAccountID(sfAccount))
{
JLOG(ctx.j.debug()) << "preflight1: Sponsor account cannot be the same as the account";
return temMALFORMED;
}
return tesSUCCESS;
}
/** Performs early sanity checks on the account and fee fields */
NotTEC
Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
@@ -230,6 +284,9 @@ Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
if (ctx.tx.isFlag(tfInnerBatchTxn) != ctx.parentBatchId.has_value())
return temINVALID_INNER_BATCH;
if (auto const ter = preflight1Sponsor(ctx); !isTesSuccess(ter))
return ter;
return tesSUCCESS;
}
@@ -343,6 +400,44 @@ Transactor::checkPermission(
return tesSUCCESS;
}
NotTEC
Transactor::checkSponsor(ReadView const& view, STTx const& tx)
{
if (!tx.isFieldPresent(sfSponsor))
return tesSUCCESS;
// Reserve sponsorship with permissioned delegation is disallowed.
if (tx.isFieldPresent(sfDelegate) && isReserveSponsored(tx))
return temINVALID;
if (!view.exists(keylet::account(tx.getAccountID(sfSponsor))))
return terNO_ACCOUNT;
// Skip Sponsorship existence checks if the sponsor has signed the transaction - this
// transaction is valid regardless of the Sponsorship object.
// The use of the Sponsorship object is properly handled in
// getFeePayer/checkReserve/increaseOwnerCount/decreaseOwnerCount.
if (tx.isFieldPresent(sfSponsorSignature))
return tesSUCCESS;
// If the transaction contains sfDelegate, the Sponsorship object should be
// between the sponsor and the delegate.
auto const sponsorshipSle =
view.read(keylet::sponsorship(tx.getAccountID(sfSponsor), tx.getInitiator()));
// sponsorship object missing for pre-funded (no co-signing) tx
if (!sponsorshipSle)
return terNO_PERMISSION;
if (isFeeSponsored(tx) && sponsorshipSle->isFlag(lsfSponsorshipRequireSignForFee))
return terNO_PERMISSION;
if (isReserveSponsored(tx) && sponsorshipSle->isFlag(lsfSponsorshipRequireSignForReserve))
return terNO_PERMISSION;
return tesSUCCESS;
}
XRPAmount
Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
{
@@ -351,6 +446,7 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
// The computation has two parts:
// * The base fee, which is the same for most transactions.
// * The additional cost of each multisignature on the transaction.
// * The additional cost of each multisignature on the sponsor.
XRPAmount const baseFee = view.fees().base;
// Each signer adds one more baseFee to the minimum required fee
@@ -358,7 +454,15 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
std::size_t const signerCount =
tx.isFieldPresent(sfSigners) ? tx.getFieldArray(sfSigners).size() : 0;
return baseFee + (signerCount * baseFee);
std::size_t sponsorSignerCount = 0;
if (tx.isFieldPresent(sfSponsorSignature))
{
auto const sponsorObj = tx.getFieldObject(sfSponsorSignature);
if (sponsorObj.isFieldPresent(sfSigners))
sponsorSignerCount += sponsorObj.getFieldArray(sfSigners).size();
}
return baseFee + ((signerCount + sponsorSignerCount) * baseFee);
}
XRPAmount
@@ -437,12 +541,51 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
if (feePaid == beast::kZero)
return tesSUCCESS;
auto const id = ctx.tx.getFeePayer();
auto const sle = ctx.view.read(keylet::account(id));
if (!sle)
return terNO_ACCOUNT;
auto const feePayer = getFeePayer(ctx.view, ctx.tx);
auto const payerSle = ctx.view.read(feePayer.keylet);
auto const balance = (*sle)[sfBalance].xrp();
if (!payerSle)
{
if (feePayer.type == FeePayerType::SponsorPreFunded)
{
// Sanity check: already checked in checkSponsor
return tefINTERNAL; // LCOV_EXCL_LINE
}
return terNO_ACCOUNT;
}
XRPAmount maxSpendable = beast::kZero;
if (feePayer.type == FeePayerType::SponsorPreFunded)
{
if (payerSle->getType() != ltSPONSORSHIP)
return tefINTERNAL; // LCOV_EXCL_LINE
if (payerSle->isFieldPresent(feePayer.balanceField))
maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp();
if (payerSle->isFieldPresent(sfMaxFee))
{
auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp();
maxSpendable = std::min(maxSpendable, cap);
}
}
else
{
if (payerSle->getType() != ltACCOUNT_ROOT)
return tefINTERNAL; // LCOV_EXCL_LINE
if (feePayer.type == FeePayerType::SponsorCoSigned)
{
auto const sponsorReserve = accountReserve(ctx.view, payerSle, ctx.j);
maxSpendable = payerSle->getFieldAmount(sfBalance).xrp() - sponsorReserve;
}
else
{
maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp();
}
}
// NOTE: Because preclaim evaluates against a static readview, it
// does not reflect fee deductions from other transactions paid by
@@ -451,12 +594,12 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
// transactions, this check may pass optimistically.
// The fee shortfall will be handled by the Transactor::reset mechanism,
// which caps the fee to the remaining actual balance.
if (balance < feePaid)
if (maxSpendable < feePaid)
{
JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(balance)
JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(maxSpendable)
<< " paid=" << to_string(feePaid);
if ((balance > beast::kZero) && !ctx.view.open())
if ((maxSpendable > beast::kZero) && !ctx.view.open())
{
// Closed ledger, non-zero balance, less than fee
return tecINSUFF_FEE;
@@ -473,16 +616,72 @@ Transactor::payFee()
{
auto const feePaid = ctx_.tx[sfFee].xrp();
auto const feePayer = ctx_.tx.getFeePayer();
auto const sle = view().peek(keylet::account(feePayer));
auto const feePayer = getFeePayer(view(), ctx_.tx);
auto const sle = view().peek(feePayer.keylet);
JLOG(j_.trace()) << "Fee payer: " + to_string(feePayer.id);
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
// Deduct the fee, so it's not available during the transaction.
// Will only write the account back if the transaction succeeds.
sle->setFieldAmount(sfBalance, sle->getFieldAmount(sfBalance) - feePaid);
if (feePayer != accountID_)
view().update(sle); // done in `apply()` for the account
if (feePaid == beast::kZero)
return tesSUCCESS;
XRPAmount balance = beast::kZero;
if (sle->isFieldPresent(feePayer.balanceField))
{
balance = sle->getFieldAmount(feePayer.balanceField).xrp();
}
else if (feePayer.balanceField != sfFeeAmount)
{
return tefINTERNAL; // LCOV_EXCL_LINE
}
// A co-signed sponsor pays the fee out of its own account balance, but must
// never be charged into its account reserve, and a pre-funded sponsorship's
// fee is capped by sfMaxFee. Mirror the spendable amount computed in
// checkFee() so both limits are enforced on the apply path too.
XRPAmount spendable = balance;
if (feePayer.type == FeePayerType::SponsorCoSigned)
{
auto const sponsorReserve = accountReserve(view(), sle, j_);
// max(balance - reserve, 0) with overflow handling
spendable = balance > sponsorReserve ? balance - sponsorReserve : beast::kZero;
}
else if (feePayer.type == FeePayerType::SponsorPreFunded && sle->isFieldPresent(sfMaxFee))
{
auto const cap = sle->getFieldAmount(sfMaxFee).xrp();
spendable = std::min(spendable, cap);
}
// Only sponsor fee-payers reject here on insufficient funds. For an
// ordinary account, the fee falls through and is capped by reset(), which
// caps to the account's balance. That capping is wrong for sponsors: a
// co-signed sponsor would be charged into its own reserve, and a prefunded
// sponsorship's fee amount should be rejected rather than partially spent.
if (feePaid > spendable &&
(feePayer.type == FeePayerType::SponsorPreFunded ||
feePayer.type == FeePayerType::SponsorCoSigned))
{
if ((spendable > beast::kZero) && !view().open())
return tecINSUFF_FEE;
return terINSUF_FEE_B;
}
auto const feeAmountAfter = balance - feePaid;
if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount)
{
// Because ltSponsorship.sfFeeAmount is soeOptional
sle->makeFieldAbsent(feePayer.balanceField);
}
else
{
sle->setFieldAmount(feePayer.balanceField, feeAmountAfter);
}
view().update(sle);
// VFALCO Should we call view().rawDestroyXRP() here as well?
return tesSUCCESS;
@@ -656,7 +855,7 @@ Transactor::ticketDelete(
}
// Update the Ticket owner's reserve.
adjustOwnerCount(view, sleAccount, -1, j);
decreaseOwnerCountForObject(view, sleAccount, sleTicket, 1, j);
// Remove Ticket from ledger.
view.erase(sleTicket);
@@ -750,6 +949,21 @@ Transactor::checkSign(
return tesSUCCESS;
}
if (sigObject.isFieldPresent(sfSponsorSignature))
{
// Co-signed sponsorship
// Sanity check: already checked in preflight1
if (!sigObject.isFieldPresent(sfSponsor))
return tefINTERNAL; // LCOV_EXCL_LINE
auto const sponsorID = sigObject.getAccountID(sfSponsor);
auto const sponsorSignature = sigObject.getFieldObject(sfSponsorSignature);
if (auto const ret = checkSign(view, flags, std::nullopt, sponsorID, sponsorSignature, j);
!isTesSuccess(ret))
return ret;
}
// If the pk is empty and not simulate or simulate and signers,
// then we must be multi-signing.
if (sigObject.isFieldPresent(sfSigners))
@@ -1079,11 +1293,38 @@ Transactor::reset(XRPAmount fee)
if (!txnAcct)
return {tefINTERNAL, beast::kZero};
auto const payerSle = view().peek(keylet::account(ctx_.tx.getFeePayer()));
auto const feePayer = getFeePayer(view(), ctx_.tx);
auto const payerSle = view().peek(feePayer.keylet);
if (!payerSle)
return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
auto const balance = payerSle->getFieldAmount(sfBalance).xrp();
XRPAmount balance = beast::kZero;
if (payerSle->isFieldPresent(feePayer.balanceField))
{
balance = payerSle->getFieldAmount(feePayer.balanceField).xrp();
}
else if (feePayer.balanceField != sfFeeAmount)
{
return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
}
if (feePayer.type == FeePayerType::SponsorPreFunded && payerSle->isFieldPresent(sfMaxFee))
{
auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp();
fee = std::min(fee, cap);
}
// A co-signed sponsor must never be charged into its own account reserve,
// so the fee is capped to the balance above the reserve rather than to the
// full balance.
XRPAmount spendable = balance;
if (feePayer.type == FeePayerType::SponsorCoSigned)
{
auto const sponsorReserve = accountReserve(view(), payerSle, j_);
// max(balance - reserve, 0) with overflow handling
spendable = balance > sponsorReserve ? balance - sponsorReserve : beast::kZero;
}
// balance should have already been checked in checkFee / preFlight.
XRPL_ASSERT(
@@ -1093,8 +1334,8 @@ Transactor::reset(XRPAmount fee)
// We retry/reject the transaction if the account balance is zero or
// we're applying against an open ledger and the balance is less than
// the fee
if (fee > balance)
fee = balance;
if (fee > spendable)
fee = spendable;
// Since we reset the context, we need to charge the fee and update
// the account's sequence number (or consume the Ticket) again.
@@ -1102,7 +1343,17 @@ Transactor::reset(XRPAmount fee)
// If for some reason we are unable to consume the ticket or sequence
// then the ledger is corrupted. Rather than make things worse we
// reject the transaction.
payerSle->setFieldAmount(sfBalance, balance - fee);
auto const feeAmountAfter = balance - fee;
if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount)
{
// Because ltSponsorship.sfFeeAmount is soeOptional
payerSle->makeFieldAbsent(feePayer.balanceField);
}
else
{
payerSle->setFieldAmount(feePayer.balanceField, feeAmountAfter);
}
TER const ter{consumeSeqProxy(txnAcct)};
XRPL_ASSERT(isTesSuccess(ter), "xrpl::Transactor::reset : result is tesSUCCESS");
@@ -1116,6 +1367,48 @@ Transactor::reset(XRPAmount fee)
return {ter, fee};
}
FeePayer
Transactor::getFeePayer(ReadView const& view, STTx const& tx)
{
if (tx.isFieldPresent(sfSponsor) && isFeeSponsored(tx))
{
auto const sponsorID = tx.getAccountID(sfSponsor);
auto const sponseeID = tx.getInitiator();
auto const sponsorshipKeylet = keylet::sponsorship(sponsorID, sponseeID);
// if pre-funded sponsorship exists, prefer it
if (view.exists(sponsorshipKeylet))
{
// pre funded
return FeePayer{
.id = sponsorID,
.keylet = sponsorshipKeylet,
.balanceField = sfFeeAmount,
.type = FeePayerType::SponsorPreFunded};
}
// Checked in Transactor::checkSponsor
XRPL_ASSERT(
tx.isFieldPresent(sfSponsorSignature),
"xrpl::getFeePayer has sponsor signature without a sponsorship object");
// co-signed
return FeePayer{
.id = sponsorID,
.keylet = keylet::account(sponsorID),
.balanceField = sfBalance,
.type = FeePayerType::SponsorCoSigned};
}
AccountID const payerID = tx.getInitiator();
auto const payerAccountKeylet = keylet::account(payerID);
auto const payerType =
tx.isFieldPresent(sfDelegate) ? FeePayerType::Delegate : FeePayerType::Account;
return FeePayer{
.id = payerID, .keylet = payerAccountKeylet, .balanceField = sfBalance, .type = payerType};
}
// The sole purpose of this function is to provide a convenient, named
// location to set a breakpoint, to be used when replaying transactions.
void
+3
View File
@@ -181,6 +181,9 @@ invokePreclaim(PreclaimContext const& ctx)
if (NotTEC const result = T::checkPriorTxAndLastLedger(ctx))
return result;
if (NotTEC const result = T::checkSponsor(ctx.view, ctx.tx))
return result;
if (NotTEC const result =
Transactor::invokeCheckPermission<T>(ctx.view, ctx.tx))
return result;
+41 -1
View File
@@ -148,6 +148,15 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
if (isXRP((*before)[sfAmount]))
drops_ -= (*before)[sfAmount].xrp().drops();
break;
case ltSPONSORSHIP:
if (before->isFieldPresent(sfFeeAmount))
{
XRPL_ASSERT(
isXRP((*before)[sfFeeAmount]),
"XRPNotCreated::visitEntry : Sponsorship.FeeAmount is XRP");
drops_ -= (*before)[sfFeeAmount].xrp().drops();
}
break;
default:
break;
}
@@ -168,6 +177,15 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
if (!isDelete && isXRP((*after)[sfAmount]))
drops_ += (*after)[sfAmount].xrp().drops();
break;
case ltSPONSORSHIP:
if (!isDelete && after->isFieldPresent(sfFeeAmount))
{
XRPL_ASSERT(
isXRP((*after)[sfFeeAmount]),
"XRPNotCreated::visitEntry : Sponsorship.FeeAmount is XRP");
drops_ += (*after)[sfFeeAmount].xrp().drops();
}
break;
default:
break;
}
@@ -515,6 +533,20 @@ AccountRootsDeletedClean::finalize(
if (enforce)
return false;
}
// An account should not be deleted with sponsorship fields
if (after->isFieldPresent(sfSponsoredOwnerCount) ||
after->isFieldPresent(sfSponsoringOwnerCount) ||
after->isFieldPresent(sfSponsoringAccountCount) || after->isFieldPresent(sfSponsor))
{
JLOG(j.fatal()) << "Invariant failed: account deletion left "
"behind a sponsorship field";
XRPL_ASSERT(
enforce,
"xrpl::AccountRootsDeletedClean::finalize : "
"deleted account has no sponsorship fields");
if (enforce)
return false;
}
// Simple types
for (auto const& [keyletfunc, _1, _2] : kDirectAccountKeylets)
{
@@ -881,8 +913,10 @@ ValidPseudoAccounts::visitEntry(bool isDelete, SLE::const_ref before, SLE::const
// 1. Exactly one of the pseudo-account fields is set.
// 2. The sequence number is not changed.
// 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth
// flags are set.
// flags are set.
// 4. The RegularKey is not set.
// 5. The SponsoredOwnerCount, SponsoringOwnerCount, SponsoringAccountCount, Sponsor
// fields are not set.
{
std::vector<SField const*> const& fields = getPseudoAccountFields();
@@ -908,6 +942,12 @@ ValidPseudoAccounts::visitEntry(bool isDelete, SLE::const_ref before, SLE::const
{
errors_.emplace_back("pseudo-account has a regular key");
}
if (after->isFieldPresent(sfSponsoredOwnerCount) ||
after->isFieldPresent(sfSponsoringOwnerCount) || after->isFieldPresent(sfSponsor) ||
after->isFieldPresent(sfSponsoringAccountCount))
{
errors_.emplace_back("pseudo-account has a sponsorship field");
}
}
}
}
@@ -0,0 +1,158 @@
#include <xrpl/tx/invariants/SponsorshipInvariant.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <cstdint>
namespace xrpl {
// Add new sponsorship-related invariants implementations
void
SponsorshipOwnerCountsMatch::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
{
auto getSponsored = [](SLE::const_ref sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoredOwnerCount);
return 0;
};
auto getSponsoring = [](SLE::const_ref sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoringOwnerCount);
return 0;
};
auto getOwnerCount = [](SLE::const_ref sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfOwnerCount);
return 0;
};
auto getSponsoredObjectOwnerCount = [&](SLE::const_ref sle) -> std::uint32_t {
if (!sle)
return 0;
switch (sle->getType())
{
case ltACCOUNT_ROOT:
return 0;
case ltRIPPLE_STATE: {
// A trust line can be reserve-sponsored independently on each
// side, so it may contribute up to two sponsored owner counts.
uint32_t ownerCount = 0;
if (sle->isFieldPresent(sfHighSponsor))
ownerCount++;
if (sle->isFieldPresent(sfLowSponsor))
ownerCount++;
return ownerCount;
}
default:
// Every other supported type carries a single sfSponsor field
// and contributes its full owner-count magnitude only when it is
// sponsored.
if (!sle->isFieldPresent(sfSponsor))
return 0;
return getLedgerEntryOwnerCount(*sle);
}
};
// The values are implicitly casted to std::int64_t to calculate deltas.
std::int64_t const beforeSponsored = getSponsored(before);
std::int64_t const afterSponsored = getSponsored(after);
std::int64_t const beforeSponsoring = getSponsoring(before);
std::int64_t const afterSponsoring = getSponsoring(after);
std::int64_t const beforeSponsoredObjectOwnerCount = getSponsoredObjectOwnerCount(before);
std::int64_t const afterSponsoredObjectOwnerCount =
isDelete ? 0 : getSponsoredObjectOwnerCount(after);
deltaSponsoredOwnerCount_ += (afterSponsored - beforeSponsored);
deltaSponsoringOwnerCount_ += (afterSponsoring - beforeSponsoring);
deltaSponsoredObjectOwnerCount_ +=
(afterSponsoredObjectOwnerCount - beforeSponsoredObjectOwnerCount);
if (getOwnerCount(after) < getSponsored(after))
ownerCountBelowSponsored_ += 1;
}
bool
SponsorshipOwnerCountsMatch::finalize(
STTx const&,
TER const,
XRPAmount const,
ReadView const&,
beast::Journal const& j) const
{
if (deltaSponsoredOwnerCount_ != deltaSponsoringOwnerCount_)
{
JLOG(j.fatal()) << "Invariant failed: SponsoredOwnerCount does not "
"equal SponsoringOwnerCount delta.";
return false;
}
if (ownerCountBelowSponsored_ > 0)
{
JLOG(j.fatal())
<< "Invariant failed: OwnerCount must be greater than or equal to SponsoredOwnerCount.";
return false;
}
if (deltaSponsoredObjectOwnerCount_ != deltaSponsoredOwnerCount_)
{
JLOG(j.fatal()) << "Invariant failed: SponsoredObjectOwnerCount does not "
"equal SponsoredOwnerCount delta.";
return false;
}
return true;
}
void
SponsorshipAccountCountMatchesField::visitEntry(bool, SLE::const_ref before, SLE::const_ref after)
{
auto getSponsoringAccountCount = [](SLE::const_ref sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoringAccountCount);
return 0;
};
auto hasSponsorField = [](SLE::const_ref sle) -> bool {
return sle && sle->getType() == ltACCOUNT_ROOT && sle->isFieldPresent(sfSponsor);
};
std::int64_t const beforeCount = getSponsoringAccountCount(before);
std::int64_t const afterCount = getSponsoringAccountCount(after);
deltaSponsoringAccountCount_ += (afterCount - beforeCount);
int const beforePresent = hasSponsorField(before) ? 1 : 0;
int const afterPresent = hasSponsorField(after) ? 1 : 0;
deltaSponsorFieldPresence_ += (afterPresent - beforePresent);
}
bool
SponsorshipAccountCountMatchesField::finalize(
STTx const&,
TER const,
XRPAmount const,
ReadView const&,
beast::Journal const& j) const
{
if (deltaSponsoringAccountCount_ != deltaSponsorFieldPresence_)
{
JLOG(j.fatal()) << "Invariant failed: Net delta of SponsoringAccountCount does not "
"match net delta of sfSponsor presence.";
return false;
}
return true;
}
} // namespace xrpl
+5 -4
View File
@@ -222,7 +222,10 @@ ValidVault::deltaAssetsTxAccount(STTx const& tx, XRPAmount fee) const
if (!ret.has_value() || !vaultAsset.native())
return ret;
if (auto const delegate = tx[~sfDelegate]; delegate.has_value() && *delegate != tx[sfAccount])
// Only add the fee back if tx[sfAccount] actually paid it. When the fee is
// paid by someone else (a delegate or a fee sponsor), the
// account's XRP balance moved only by the vault amount.
if (tx.getFeePayerID() != tx[sfAccount])
return ret;
ret->delta += fee.drops();
@@ -1044,10 +1047,8 @@ ValidVault::finalize(
case ttLOAN_SET:
case ttLOAN_MANAGE:
case ttLOAN_PAY: {
// TBD
case ttLOAN_PAY:
return true;
}
default:
// LCOV_EXCL_START
+1 -1
View File
@@ -731,7 +731,7 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
// Create MPToken for the offer's owner. No need to check
// for the reserve since the offer is removed if it is consumed.
// Therefore, the owner count remains the same.
if (auto const err = checkCreateMPT(sb, assetIn.get<MPTIssue>(), owner, j_);
if (auto const err = checkCreateMPT(sb, assetIn.get<MPTIssue>(), owner, {}, j_);
!isTesSuccess(err))
{
return true;
+2 -1
View File
@@ -410,7 +410,8 @@ MPTEndpointOfferCrossingStep::checkCreateMPT(ApplyView& view, xrpl::DebtDirectio
// for the reserve since the offer doesn't go on the books
// if crossed. Insufficient reserve is allowed if the offer
// crossed. See CreateOffer::applyGuts() for reserve check.
if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, j_); !isTesSuccess(err))
if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, {}, j_);
!isTesSuccess(err))
{
JLOG(j_.trace()) << "MPTEndpointStep::checkCreateMPT: failed create MPT";
resetCache(srcDebtDir);
@@ -35,7 +35,6 @@
#include <utility>
namespace xrpl {
bool
AccountDelete::checkExtraFeatures(PreflightContext const& ctx)
{
@@ -268,6 +267,15 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
if (cp)
return tecHAS_OBLIGATIONS;
if (sleAccount->isFieldPresent(sfSponsor))
{
if (dst != sleAccount->getAccountID(sfSponsor))
return tecNO_SPONSOR_PERMISSION;
}
if (sleAccount->isFieldPresent(sfSponsoringOwnerCount) ||
sleAccount->isFieldPresent(sfSponsoringAccountCount))
return tecHAS_OBLIGATIONS;
// We don't allow an account to be deleted if its sequence number
// is within 256 of the current ledger. This prevents replay of old
// transactions if this account is resurrected after it is deleted.
@@ -394,6 +402,35 @@ AccountDelete::doApply()
(*src)[sfBalance] = (*src)[sfBalance] - remainingBalance;
ctx_.deliver(remainingBalance);
if (src->isFieldPresent(sfSponsor))
{
auto const sponsorID = src->getAccountID(sfSponsor);
auto sponsorSle = view().peek(keylet::account(sponsorID));
if (!sponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const sponsoringAccountCount = sponsorSle->getFieldU32(sfSponsoringAccountCount);
XRPL_ASSERT(
sponsoringAccountCount != 0,
"xrpl::AccountDelete::doApply : sponsoring account count is present");
if (sponsoringAccountCount == 0)
{
// sanity check
// Since sfSponsoringAccountCount is set to soeDEFAULT, the field will not be
// present with a value of 0.
return tefINTERNAL; // LCOV_EXCL_LINE
}
sponsorSle->at(sfSponsoringAccountCount) = sponsoringAccountCount - 1;
view().update(sponsorSle);
// Following line might look redundant, but without it, sfSponsor
// would end up remaining in after-ltAccountRoot during the
// InvariantCheck.
src->makeFieldAbsent(sfSponsor);
}
XRPL_ASSERT(
(*src)[sfBalance] == XRPAmount(0), "xrpl::AccountDelete::doApply : source balance is zero");
@@ -8,6 +8,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -147,9 +148,7 @@ SignerListSet::preCompute()
Transactor::preCompute();
}
// The return type is signed so it is compatible with the 3rd argument
// of adjustOwnerCount() (which must be signed).
static int
static std::uint32_t
signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const& rules)
{
// We always compute the full change in OwnerCount, taking into account:
@@ -165,7 +164,7 @@ signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const& rules)
// units. A SignerList with 8 entries would cost 10 OwnerCount units.
//
// The static_cast should always be safe since entryCount should always
// be in the range from 1 to 32.
// be in the range from 1 to 32, so the result is always positive.
// We've got a lot of room to grow.
XRPL_ASSERT(
entryCount >= STTx::kMinMultiSigners,
@@ -196,12 +195,11 @@ removeSignersFromLedger(
// There are two different ways that the OwnerCount could be managed.
// If the lsfOneOwnerCount bit is set then remove just one owner count.
// Otherwise use the pre-MultiSignReserve amendment calculation.
int removeFromOwnerCount = -1;
std::uint32_t removeFromOwnerCount = 1;
if (!signers->isFlag(lsfOneOwnerCount))
{
STArray const& actualList = signers->getFieldArray(sfSignerEntries);
removeFromOwnerCount =
signerCountBasedOwnerCountDelta(actualList.size(), view.rules()) * -1;
removeFromOwnerCount = signerCountBasedOwnerCountDelta(actualList.size(), view.rules());
}
// Remove the node from the account directory.
@@ -215,8 +213,8 @@ removeSignersFromLedger(
// LCOV_EXCL_STOP
}
adjustOwnerCount(
view, view.peek(accountKeylet), removeFromOwnerCount, registry.getJournal("View"));
decreaseOwnerCountForObject(
view, view.peek(accountKeylet), signers, removeFromOwnerCount, registry.getJournal("View"));
view.erase(signers);
@@ -315,19 +313,20 @@ SignerListSet::replaceSignerList()
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
// Compute new reserve. Verify the account has funds to meet the reserve.
std::uint32_t const oldOwnerCount{(*sle)[sfOwnerCount]};
static constexpr int kAddedOwnerCount = 1;
std::uint32_t const flags{lsfOneOwnerCount};
XRPAmount const newReserve{view().fees().accountReserve(oldOwnerCount + kAddedOwnerCount)};
// We check the reserve against the starting balance because we want to
// allow dipping into the reserve to pay fees. This behavior is consistent
// with TicketCreate.
if (preFeeBalance_ < newReserve)
return tecINSUFFICIENT_RESERVE;
if (auto const ret = checkReserve(
ctx_.getApplyViewContext(),
sle,
preFeeBalance_,
{.ownerCountDelta = kAddedOwnerCount},
ctx_.journal);
!isTesSuccess(ret))
return ret;
// Everything's ducky. Add the ltSIGNER_LIST to the ledger.
auto signerList = std::make_shared<SLE>(signerListKeylet);
@@ -349,7 +348,8 @@ SignerListSet::replaceSignerList()
// If we succeeded, the new entry counts against the
// creator's reserve.
adjustOwnerCount(view(), sle, kAddedOwnerCount, viewJ);
increaseOwnerCount(ctx_.getApplyViewContext(), sle, kAddedOwnerCount, viewJ);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), signerList);
return tesSUCCESS;
}
@@ -435,8 +435,7 @@ transferHelper(
return tecINTERNAL; // LCOV_EXCL_LINE
{
auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
auto const reserve = psb.fees().accountReserve(ownerCount);
auto const reserve = accountReserve(psb, sleSrc, j);
auto const availableBalance = [&]() -> STAmount {
STAmount curBal = (*sleSrc)[sfBalance];
@@ -726,9 +725,8 @@ finalizeClaimHelper(
}
// Remove the claim id from the ledger
decreaseOwnerCountForObject(outerSb, sleOwner, sleClaimID, 1, j);
outerSb.erase(sleClaimID);
adjustOwnerCount(outerSb, sleOwner, -1, j);
}
}
@@ -1028,7 +1026,7 @@ applyCreateAccountAttestations(
// Check reserve
auto const balance = (*sleDoor)[sfBalance];
auto const reserve = psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1);
auto const reserve = accountReserve(psb, sleDoor, j, {.ownerCountDelta = 1});
if (balance < reserve)
return std::unexpected(tecINSUFFICIENT_RESERVE);
@@ -1137,7 +1135,7 @@ applyCreateAccountAttestations(
return tecINTERNAL; // LCOV_EXCL_LINE
// Reserve was already checked
adjustOwnerCount(psb, sleDoor, 1, j);
increaseOwnerCount(psb, sleDoor, {}, 1, j);
psb.insert(createdSleClaimID);
psb.update(sleDoor);
}
@@ -1439,7 +1437,7 @@ XChainCreateBridge::preclaim(PreclaimContext const& ctx)
return terNO_ACCOUNT;
auto const balance = (*sleAcc)[sfBalance];
auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
auto const reserve = accountReserve(ctx.view, sleAcc, ctx.j, {.ownerCountDelta = 1});
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
@@ -1484,7 +1482,7 @@ XChainCreateBridge::doApply()
(*sleBridge)[sfOwnerNode] = *page;
}
adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
increaseOwnerCount(ctx_.view(), sleAcct, {}, 1, ctx_.journal);
ctx_.view().insert(sleBridge);
ctx_.view().update(sleAcct);
@@ -1987,8 +1985,7 @@ XChainCreateClaimID::preclaim(PreclaimContext const& ctx)
return terNO_ACCOUNT;
auto const balance = (*sleAcc)[sfBalance];
auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
auto const reserve = accountReserve(ctx.view, sleAcc, ctx.j, {.ownerCountDelta = 1});
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
}
@@ -2046,7 +2043,7 @@ XChainCreateClaimID::doApply()
(*sleClaimID)[sfOwnerNode] = *page;
}
adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
increaseOwnerCount(ctx_.view(), sleAcct, {}, 1, ctx_.journal);
ctx_.view().insert(sleClaimID);
ctx_.view().update(sleBridge);
@@ -91,8 +91,7 @@ CheckCancel::doApply()
}
// If we succeeded, update the check owner's reserve.
auto const sleSrc = view().peek(keylet::account(srcId));
adjustOwnerCount(view(), sleSrc, -1, viewJ);
decreaseOwnerCountForObject(view(), srcId, sleCheck, 1, viewJ);
// Remove check from ledger.
view().erase(sleCheck);
+27 -10
View File
@@ -3,11 +3,13 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/scope.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -30,7 +32,7 @@
#include <xrpl/tx/paths/detail/Steps.h>
#include <algorithm>
#include <cstdint>
#include <memory>
#include <optional>
namespace xrpl {
@@ -175,7 +177,7 @@ CheckCash::preclaim(PreclaimContext const& ctx)
// once the check is cashed, since the check's reserve will no
// longer be required. So, if we're dealing in XRP, we add one
// reserve's worth to the available funds.
if (value.native())
if (value.native() && !sleCheck->isFieldPresent(sfSponsor))
availableFunds += XRPAmount{ctx.view.fees().increment};
if (value > availableFunds)
@@ -307,6 +309,8 @@ CheckCash::doApply()
// LCOV_EXCL_STOP
}
auto const sponsorCheckSle = getLedgerEntryReserveSponsor(psb, sleCheck);
// Preclaim already checked that source has at least the requested
// funds.
//
@@ -335,7 +339,7 @@ CheckCash::doApply()
// from src's directory, we allow them to send that additional
// incremental reserve amount in the transfer. Hence the -1
// argument.
STAmount const srcLiquid{xrpLiquid(psb, srcId, -1, viewJ)};
STAmount const srcLiquid{xrpLiquid(psb, srcId, sponsorCheckSle ? 0 : -1, viewJ)};
// Now, how much do they need in order to be successful?
STAmount const xrpDeliver{
@@ -385,14 +389,25 @@ CheckCash::doApply()
STAmount const flowDeliver{
optDeliverMin ? maxDeliverMin() : ctx_.tx.getFieldAmount(sfAmount)};
auto applyViewContext = ApplyViewContext({.view = psb, .tx = ctx_.tx});
auto const sponsorSle = getTxReserveSponsor(applyViewContext);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
// Check reserve. Return destination account SLE if enough reserve,
// otherwise return nullptr.
auto checkReserve = [&]() -> SLE::pointer {
auto checkDstReserve = [&]() -> SLE::pointer {
auto sleDst = psb.peek(keylet::account(accountID_));
// Can the account cover the trust line's or MPT reserve?
if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)};
preFeeBalance_ < psb.fees().accountReserve(ownerCount + 1))
if (auto const ret = checkReserve(
applyViewContext,
sleDst,
preFeeBalance_,
*sponsorSle,
{.ownerCountDelta = 1},
j_);
!isTesSuccess(ret))
{
JLOG(j_.trace()) << "Trust line does not exist. "
"Insufficient reserve to create line.";
@@ -426,7 +441,7 @@ CheckCash::doApply()
// a. this (destination) account and
// b. issuing account (not sending account).
auto const sleDst = checkReserve();
auto const sleDst = checkDstReserve();
if (sleDst == nullptr)
return tecNO_LINE_INSUF_RESERVE;
@@ -449,6 +464,7 @@ CheckCash::doApply()
Issue(currency, accountID_), // limit of zero
0, // quality in
0, // quality out
*sponsorSle, // sponsor
viewJ); // journal
!isTesSuccess(ter))
{
@@ -491,11 +507,12 @@ CheckCash::doApply()
auto const mptokenKey = keylet::mptoken(mptID, accountID_);
if (!psb.exists(mptokenKey))
{
auto sleDst = checkReserve();
auto sleDst = checkDstReserve();
if (sleDst == nullptr)
return tecINSUFFICIENT_RESERVE;
if (auto const err = checkCreateMPT(psb, mptID, accountID_, j_);
if (auto const err =
checkCreateMPT(psb, mptID, accountID_, *sponsorSle, j_);
!isTesSuccess(err))
{
return err;
@@ -581,7 +598,7 @@ CheckCash::doApply()
}
// If we succeeded, update the check owner's reserve.
adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), -1, viewJ);
decreaseOwnerCountForObject(psb, srcId, sleCheck, 1, viewJ);
// Remove check from ledger.
psb.erase(sleCheck);
@@ -7,6 +7,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -194,13 +195,10 @@ CheckCreate::doApply()
// A check counts against the reserve of the issuing account, but we
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
{
STAmount const reserve{view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
if (auto const ret = checkReserve(
ctx_.getApplyViewContext(), sle, preFeeBalance_, {.ownerCountDelta = 1}, ctx_.journal);
!isTesSuccess(ret))
return ret;
// Note that we use the value from the sequence or ticket as the
// Check sequence. For more explanation see comments in SeqProxy.h.
std::uint32_t const seq = ctx_.tx.getSeqValue();
@@ -253,7 +251,9 @@ CheckCreate::doApply()
sleCheck->setFieldU64(sfOwnerNode, *page);
}
// If we succeeded, the new entry counts against the creator's reserve.
adjustOwnerCount(view(), sle, 1, viewJ);
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, viewJ);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCheck);
return tesSUCCESS;
}
@@ -4,6 +4,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -11,7 +12,6 @@
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
@@ -93,12 +93,19 @@ CredentialAccept::doApply()
if (!sleSubject || !sleIssuer)
return tefINTERNAL; // LCOV_EXCL_LINE
{
STAmount const reserve{
view().fees().accountReserve(sleSubject->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
auto txSponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext());
if (!txSponsorSle)
return txSponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx_.getApplyViewContext(),
sleSubject,
preFeeBalance_,
*txSponsorSle,
{.ownerCountDelta = 1},
j_);
!isTesSuccess(ret))
return ret;
auto const credType(ctx_.tx[sfCredentialType]);
Keylet const credentialKey = keylet::credential(accountID_, issuer, credType);
@@ -115,10 +122,16 @@ CredentialAccept::doApply()
}
sleCred->setFieldU32(sfFlags, lsfAccepted);
view().update(sleCred);
adjustOwnerCount(view(), sleIssuer, -1, j_);
adjustOwnerCount(view(), sleSubject, 1, j_);
// Release the original creation sponsor from the credential (it covered
// the issuer's reserve), then assign the accept tx's sponsor (if any) so
// the credential reflects whoever is now covering the subject's reserve.
decreaseOwnerCountForObject(view(), sleIssuer, sleCred, 1, j_);
removeSponsorFromLedgerEntry(sleCred);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCred);
increaseOwnerCount(ctx_.getApplyViewContext(), sleSubject, 1, j_);
view().update(sleCred);
return tesSUCCESS;
}
@@ -7,13 +7,13 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h> // IWYU pragma: keep
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
@@ -130,12 +130,10 @@ CredentialCreate::doApply()
if (!sleIssuer)
return tefINTERNAL; // LCOV_EXCL_LINE
{
STAmount const reserve{
view().fees().accountReserve(sleIssuer->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
if (auto const ret = checkReserve(
ctx_.getApplyViewContext(), sleIssuer, preFeeBalance_, {.ownerCountDelta = 1}, j_);
!isTesSuccess(ret))
return ret;
sleCred->setAccountID(sfSubject, subject);
sleCred->setAccountID(sfIssuer, accountID_);
@@ -153,7 +151,8 @@ CredentialCreate::doApply()
return tecDIR_FULL;
sleCred->setFieldU64(sfIssuerNode, *page);
adjustOwnerCount(view(), sleIssuer, 1, j_);
increaseOwnerCount(ctx_.getApplyViewContext(), sleIssuer, 1, j_);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCred);
}
if (subject == accountID_)
@@ -5,10 +5,10 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
@@ -98,11 +98,14 @@ DelegateSet::doApply()
if (permissions.empty())
return tecINTERNAL; // LCOV_EXCL_LINE
STAmount const reserve{
ctx_.view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
if (auto const ret = checkReserve(
ctx_.getApplyViewContext(),
sleOwner,
preFeeBalance_,
{.ownerCountDelta = 1},
ctx_.journal);
!isTesSuccess(ret))
return ret;
sle = std::make_shared<SLE>(delegateKey);
sle->setAccountID(sfAccount, accountID_);
@@ -130,7 +133,8 @@ DelegateSet::doApply()
(*sle)[sfDestinationNode] = *destPage;
ctx_.view().insert(sle);
adjustOwnerCount(ctx_.view(), sleOwner, 1, ctx_.journal);
increaseOwnerCount(ctx_.getApplyViewContext(), sleOwner, 1, ctx_.journal);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sle);
return tesSUCCESS;
}
@@ -170,7 +174,7 @@ DelegateSet::deleteDelegate(ApplyView& view, SLE::ref sle, beast::Journal j)
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleOwner, -1, j);
decreaseOwnerCountForObject(view, sleOwner, sle, 1, j);
view.erase(sle);
+16 -3
View File
@@ -332,17 +332,30 @@ applyCreate(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Jou
return err;
}
if (auto const err = createMPToken(sb, mptID, accountId, flags); !isTesSuccess(err))
if (auto const err = createMPToken(sb, mptID, accountId, {}, flags);
!isTesSuccess(err))
return err;
// Don't adjust AMM owner count.
// It's irrelevant for pseudo-account like AMM.
return accountSend(
sb, account, accountId, amount, ctx.journal, WaiveTransferFee::Yes);
sb,
account,
accountId,
amount,
ctx.journal,
{}, // don't sponsor for AMM Trustline
WaiveTransferFee::Yes);
},
// Set AMM flag on AMM trustline
[&](Issue const& issue) -> TER {
if (auto const res = accountSend(
sb, account, accountId, amount, ctx.journal, WaiveTransferFee::Yes))
sb,
account,
accountId,
amount,
ctx.journal,
{}, // don't sponsor for AMM Trustline
WaiveTransferFee::Yes))
return res;
// Set AMM flag on AMM trustline
if (!isXRP(amount))
@@ -618,7 +618,13 @@ AMMDeposit::deposit(
}
auto res = accountSend(
view, accountID_, ammAccount, amountDepositActual, ctx_.journal, WaiveTransferFee::Yes);
view,
accountID_,
ammAccount,
amountDepositActual,
ctx_.journal,
{}, // don't sponsor for AMM Trustline
WaiveTransferFee::Yes);
if (!isTesSuccess(res))
{
JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit " << amountDepositActual;
@@ -642,6 +648,7 @@ AMMDeposit::deposit(
ammAccount,
*amount2DepositActual,
ctx_.journal,
{}, // don't sponsor for AMM Trustline
WaiveTransferFee::Yes);
if (!isTesSuccess(res))
{
@@ -7,6 +7,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
@@ -658,15 +659,15 @@ AMMWithdraw::withdraw(
auto sleAccount = view.peek(keylet::account(account));
if (!sleAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
STAmount const balance = (*sleAccount)[sfBalance];
std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
auto const balance = (*sleAccount)[sfBalance]->xrp();
// See also TrustSet::doApply() and MPTokenAuthorize::authorize()
XRPAmount const reserve(
(ownerCount < 2) ? XRPAmount(beast::kZero)
: view.fees().accountReserve(ownerCount + 1));
(ownerCount(sleAccount, journal) < 2)
? XRPAmount(beast::kZero)
: accountReserve(view, sleAccount, journal, {.ownerCountDelta = 1}));
auto const balanceAdj = isIssue ? std::max(priorBalance, balance.xrp()) : priorBalance;
auto const balanceAdj = isIssue ? std::max(priorBalance, balance) : priorBalance;
if (balanceAdj < reserve)
return tecINSUFFICIENT_RESERVE;
}
@@ -683,7 +684,7 @@ AMMWithdraw::withdraw(
!isTesSuccess(err))
return err;
if (auto const err = checkCreateMPT(view, mptIssue, account, journal);
if (auto const err = checkCreateMPT(view, mptIssue, account, {}, journal);
!isTesSuccess(err))
{
return err;
@@ -700,7 +701,7 @@ AMMWithdraw::withdraw(
// Withdraw amountWithdraw
auto res = accountSend(
view, ammAccount, account, amountWithdrawActual, journal, WaiveTransferFee::Yes);
view, ammAccount, account, amountWithdrawActual, journal, {}, WaiveTransferFee::Yes);
if (!isTesSuccess(res))
{
// LCOV_EXCL_START
@@ -719,7 +720,7 @@ AMMWithdraw::withdraw(
return {res, STAmount{}, STAmount{}, STAmount{}};
res = accountSend(
view, ammAccount, account, *amount2WithdrawActual, journal, WaiveTransferFee::Yes);
view, ammAccount, account, *amount2WithdrawActual, journal, {}, WaiveTransferFee::Yes);
if (!isTesSuccess(res))
{
// LCOV_EXCL_START
@@ -833,9 +833,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
return {tefINTERNAL, false};
{
XRPAmount const reserve =
sb.fees().accountReserve(sleCreator->getFieldU32(sfOwnerCount) + 1);
XRPAmount const reserve = accountReserve(sb, sleCreator, viewJ, {.ownerCountDelta = 1});
if (preFeeBalance_ < reserve)
{
// If we are here, the signing account had an insufficient reserve
@@ -869,7 +867,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
}
// Update owner count.
adjustOwnerCount(sb, sleCreator, 1, viewJ);
increaseOwnerCount(sb, sleCreator, {}, 1, viewJ);
JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.asset()) << " : "
<< to_string(saTakerGets.asset())
+1 -2
View File
@@ -50,8 +50,7 @@ DIDDelete::deleteSLE(ApplyView& view, SLE::pointer sle, AccountID const owner, b
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleOwner, -1, j);
view.update(sleOwner);
decreaseOwnerCountForObject(view, sleOwner, sle, 1, j);
// Remove object from ledger
view.erase(sle);
+3 -2
View File
@@ -72,7 +72,8 @@ addSLE(ApplyContext& ctx, SLE::ref sle, AccountID const& owner)
// Check reserve availability for new object creation
{
auto const balance = STAmount((*sleAccount)[sfBalance]).xrp();
auto const reserve = ctx.view().fees().accountReserve((*sleAccount)[sfOwnerCount] + 1);
auto const reserve =
accountReserve(ctx.view(), sleAccount, ctx.journal, {.ownerCountDelta = 1});
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
@@ -89,7 +90,7 @@ addSLE(ApplyContext& ctx, SLE::ref sle, AccountID const& owner)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*sle)[sfOwnerNode] = *page;
}
adjustOwnerCount(ctx.view(), sleAccount, 1, ctx.journal);
increaseOwnerCount(ctx.view(), sleAccount, {}, 1, ctx.journal);
ctx.view().update(sleAccount);
return tesSUCCESS;
@@ -181,7 +181,7 @@ EscrowCancel::doApply()
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.view(),
ctx_.getApplyViewContext(),
kParityRate,
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : slep,
preFeeBalance_,
@@ -209,8 +209,7 @@ EscrowCancel::doApply()
}
}
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
ctx_.view().update(sle);
decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal);
// Remove escrow from ledger
ctx_.view().erase(slep);
@@ -11,6 +11,7 @@
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
@@ -434,16 +435,33 @@ EscrowCreate::doApply()
// Check reserve and funds availability
STAmount const amount{ctx_.tx[sfAmount]};
auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
auto const balance = sle->getFieldAmount(sfBalance).xrp();
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
// First check: whoever is on the hook for the new owner increment
// can cover it. When sponsored this hits the sponsor branch and
// validates the sponsor's reserve + remaining credit. When
// unsponsored this hits the source branch and validates the
// source's pre-lock balance against base + (currentOC+1)*increment.
if (auto const ret =
checkReserve(ctx_.getApplyViewContext(), sle, balance, {.ownerCountDelta = 1}, j_);
!isTesSuccess(ret))
return ret;
// Check reserve and funds availability
if (isXRP(amount))
{
if (balance < reserve + STAmount(amount).xrp())
// Second check (XRP escrow only): after locking the escrowed
// amount, the source must still meet its own reserve floor. This is
// always the source's own balance against the source's own reserve —
// the sponsor's reserve was already validated above, and a sponsor
// never covers the locked funds. We compare directly (rather than via
// checkReserve) because that helper diverts to the sponsor's balance
// when a sponsor is present and would ignore the source's post-lock
// balance entirely. ownerCountDelta differs by case:
// - sponsored: 0 — sponsor covers the new owner increment, so the
// source only owes reserve for its current owners.
// - unsponsored: 1 — source owes reserve including the new increment.
auto const sourceReserve = accountReserve(
ctx_.view(), sle, j_, {.ownerCountDelta = getTxReserveSponsorID(ctx_.tx) ? 0 : 1});
if (balance - STAmount(amount).xrp() < sourceReserve)
return tecUNFUNDED;
}
@@ -535,7 +553,8 @@ EscrowCreate::doApply()
}
// increment owner count
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep);
ctx_.view().update(sle);
return tesSUCCESS;
}
@@ -340,6 +340,16 @@ EscrowFinish::doApply()
}
}
// With the Sponsor amendment, release the escrow reserve before delivery.
// Token delivery can auto-create a destination holding, and the same
// sponsor (or the same account, for a self-escrow) may cover both the
// escrow being removed and the holding being created. Without the
// amendment, keep the legacy order: releasing early changes the reserve
// arithmetic for self-escrows and would break consensus if not gated.
bool const sponsorEnabled = ctx_.view().rules().enabled(featureSponsor);
if (sponsorEnabled)
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
STAmount const amount = slep->getFieldAmount(sfAmount);
// Transfer amount to destination
if (isXRP(amount))
@@ -359,7 +369,7 @@ EscrowFinish::doApply()
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.view(),
ctx_.getApplyViewContext(),
lockedRate,
sled,
preFeeBalance_,
@@ -389,10 +399,9 @@ EscrowFinish::doApply()
ctx_.view().update(sled);
// Adjust source owner count
auto const sle = ctx_.view().peek(keylet::account(account));
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
ctx_.view().update(sle);
// Adjust source owner count (legacy position, pre-Sponsor)
if (!sponsorEnabled)
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
// Remove escrow from ledger
ctx_.view().erase(slep);
@@ -372,7 +372,7 @@ LoanBrokerCoverClawback::doApply()
associateAsset(*sleBroker, vaultAsset);
// Transfer assets from pseudo-account to depositor.
return accountSend(view(), brokerPseudoID, account, clawAmount, j_, WaiveTransferFee::Yes);
return accountSend(view(), brokerPseudoID, account, clawAmount, j_, {}, WaiveTransferFee::Yes);
}
void
@@ -176,7 +176,7 @@ LoanBrokerCoverDeposit::doApply()
// Transfer assets from depositor to pseudo-account.
if (auto ter =
accountSend(view(), accountID_, brokerPseudoID, amount, j_, WaiveTransferFee::Yes))
accountSend(view(), accountID_, brokerPseudoID, amount, j_, {}, WaiveTransferFee::Yes))
return ter;
// Increase the LoanBroker's CoverAvailable by Amount
@@ -211,7 +211,14 @@ LoanBrokerCoverWithdraw::doApply()
associateAsset(*broker, vaultAsset);
return doWithdraw(view(), tx, accountID_, dstAcct, brokerPseudoID, preFeeBalance_, amount, j_);
return doWithdraw(
ctx_.getApplyViewContext(),
accountID_,
dstAcct,
brokerPseudoID,
preFeeBalance_,
amount,
j_);
}
void
@@ -155,11 +155,11 @@ LoanBrokerDelete::doApply()
{
auto const coverAvailable = STAmount{vaultAsset, broker->at(sfCoverAvailable)};
if (auto const ter = accountSend(
view(), brokerPseudoID, accountID_, coverAvailable, j_, WaiveTransferFee::Yes))
view(), brokerPseudoID, accountID_, coverAvailable, j_, {}, WaiveTransferFee::Yes))
return ter;
}
if (auto ter = removeEmptyHolding(view(), brokerPseudoID, vaultAsset, j_))
if (auto ter = removeEmptyHolding(ctx_.getApplyViewContext(), brokerPseudoID, vaultAsset, j_))
return ter;
auto brokerPseudoSLE = view().peek(keylet::account(brokerPseudoID));
@@ -186,8 +186,6 @@ LoanBrokerDelete::doApply()
view().erase(brokerPseudoSLE);
view().erase(broker);
{
auto owner = view().peek(keylet::account(accountID_));
if (!owner)
@@ -195,9 +193,11 @@ LoanBrokerDelete::doApply()
// Decreases the owner count by two: one for the LoanBroker object, and
// one for the pseudo-account.
adjustOwnerCount(view(), owner, -2, j_);
decreaseOwnerCountForObject(view(), owner, broker, 2, j_);
}
view().erase(broker);
associateAsset(*broker, vaultAsset);
return tesSUCCESS;
@@ -238,9 +238,8 @@ LoanBrokerSet::doApply()
// Increases the owner count by two: one for the LoanBroker object, and
// one for the pseudo-account.
adjustOwnerCount(view, owner, 2, j_);
auto const ownerCount = owner->at(sfOwnerCount);
if (preFeeBalance_ < view.fees().accountReserve(ownerCount))
increaseOwnerCount(view, owner, {}, 2, j_);
if (preFeeBalance_ < accountReserve(view, owner, j_))
return tecINSUFFICIENT_RESERVE;
auto maybePseudo = createPseudoAccount(view, broker->key(), sfLoanBrokerID);
@@ -249,7 +248,8 @@ LoanBrokerSet::doApply()
auto& pseudo = *maybePseudo;
auto pseudoId = pseudo->at(sfAccount);
if (auto ter = addEmptyHolding(view, pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_))
if (auto ter = addEmptyHolding(
ctx_.getApplyViewContext(), pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_))
return ter;
// Initialize data fields:
@@ -107,9 +107,8 @@ LoanDelete::doApply()
view.erase(loanSle);
// Decrement the LoanBroker's owner count.
// The broker's owner count is solely for the number of outstanding loans,
// and is distinct from the broker's pseudo-account's owner count
adjustOwnerCount(view, brokerSle, -1, j_);
adjustLoanBrokerOwnerCount(view, brokerSle, -1, j_);
// If there are no loans left, then any remaining debt must be forgiven,
// because there is no other way to pay it back.
if (brokerSle->at(sfOwnerCount) == 0)
@@ -129,7 +128,7 @@ LoanDelete::doApply()
}
}
// Decrement the borrower's owner count
adjustOwnerCount(view, borrowerSle, -1, j_);
decreaseOwnerCountForObject(view, borrowerSle, loanSle, 1, j_);
// These associations shouldn't do anything, but do them just to be safe
associateAsset(*loanSle, vaultAsset);
@@ -292,6 +292,7 @@ LoanManage::defaultLoan(
vaultSle->at(sfAccount),
STAmount{vaultAsset, defaultCovered},
j,
{},
WaiveTransferFee::Yes);
}
@@ -625,7 +625,11 @@ LoanPay::doApply()
{
// The broker may have deleted their holding. Recreate it if needed
if (auto const ter = addEmptyHolding(
view, brokerPayee, brokerPayeeSle->at(sfBalance).value().xrp(), asset, j_);
ctx_.getApplyViewContext(),
brokerPayee,
brokerPayeeSle->at(sfBalance).value().xrp(),
asset,
j_);
ter && ter != tecDUPLICATE)
{
// ignore tecDUPLICATE. That means the holding already exists,
+18 -8
View File
@@ -8,6 +8,7 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -56,6 +57,12 @@ LoanSet::preflight(PreflightContext const& ctx)
auto const& tx = ctx.tx;
if (tx.isFieldPresent(sfSponsorFlags) && isReserveSponsored(tx))
{
JLOG(ctx.j.debug()) << "LoanSet: reserve sponsorship is not allowed.";
return temINVALID_FLAG;
}
// Special case for Batch inner transactions
if (tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatchV1_1) &&
!tx.isFieldPresent(sfCounterparty))
@@ -512,12 +519,12 @@ LoanSet::doApply()
}
}
adjustOwnerCount(view, borrowerSle, 1, j_);
increaseOwnerCount(view, borrowerSle, {}, 1, j_);
{
auto const ownerCount = borrowerSle->at(sfOwnerCount);
auto const balance =
accountID_ == borrower ? preFeeBalance_ : borrowerSle->at(sfBalance).value().xrp();
if (balance < view.fees().accountReserve(ownerCount))
if (balance < accountReserve(view, borrowerSle, j_))
return tecINSUFFICIENT_RESERVE;
}
@@ -531,8 +538,9 @@ LoanSet::doApply()
borrower == accountID_ || borrower == counterparty,
"xrpl::LoanSet::doApply",
"borrower signed transaction");
auto applyViewContext = ctx_.getApplyViewContext();
if (auto const ter = addEmptyHolding(
view, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset, j_);
applyViewContext, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset, j_);
ter && ter != tecDUPLICATE)
{
// ignore tecDUPLICATE. That means the holding already exists, and
@@ -555,7 +563,11 @@ LoanSet::doApply()
"broker owner signed transaction");
if (auto const ter = addEmptyHolding(
view, brokerOwner, brokerOwnerSle->at(sfBalance).value().xrp(), vaultAsset, j_);
applyViewContext,
brokerOwner,
brokerOwnerSle->at(sfBalance).value().xrp(),
vaultAsset,
j_);
ter && ter != tecDUPLICATE)
{
// ignore tecDUPLICATE. That means the holding already exists,
@@ -631,9 +643,7 @@ LoanSet::doApply()
// Update the balances in the loan broker
adjustImpreciseNumber(brokerSle->at(sfDebtTotal), newDebtDelta, vaultAsset, vaultScale);
// The broker's owner count is solely for the number of outstanding loans,
// and is distinct from the broker's pseudo-account's owner count
adjustOwnerCount(view, brokerSle, 1, j_);
adjustLoanBrokerOwnerCount(view, brokerSle, 1, j_);
loanSequenceProxy += 1;
// The sequence should be extremely unlikely to roll over, but fail if it
// does
@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
@@ -387,8 +388,7 @@ NFTokenAcceptOffer::transferNFToken(
auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount);
if (buyerOwnerCountAfter > buyerOwnerCountBefore)
{
if (auto const reserve = view().fees().accountReserve(buyerOwnerCountAfter);
buyerBalance < reserve)
if (buyerBalance < accountReserve(view(), sleBuyer, j_))
return tecINSUFFICIENT_RESERVE;
}
@@ -3,6 +3,7 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
@@ -331,12 +332,14 @@ NFTokenMint::doApply()
// allows NFTs to be added to the page (and burn fees) without
// requiring the reserve to be met each time. The reserve is
// only managed when a new NFT page or sell offer is added.
if (auto const ownerCountAfter =
view().read(keylet::account(accountID_))->getFieldU32(sfOwnerCount);
auto const sleAccount = view().read(keylet::account(accountID_));
if (!sleAccount)
return tefINTERNAL; // LCOV_EXCL_LINE
if (auto const ownerCountAfter = sleAccount->getFieldU32(sfOwnerCount);
ownerCountAfter > ownerCountBefore)
{
if (auto const reserve = view().fees().accountReserve(ownerCountAfter);
preFeeBalance_ < reserve)
if (preFeeBalance_ < accountReserve(view(), sleAccount, j_))
return tecINSUFFICIENT_RESERVE;
}
return tesSUCCESS;
@@ -4,6 +4,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/OracleHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
@@ -13,6 +14,8 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <cstdint>
namespace xrpl {
NotTEC
@@ -68,10 +71,8 @@ OracleDelete::deleteOracle(
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const count = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? -2 : -1;
adjustOwnerCount(view, sleOwner, count, j);
std::uint32_t const count = calculateOracleReserve(sle);
decreaseOwnerCountForObject(view, sleOwner, sle, count, j);
view.erase(sle);
return tesSUCCESS;
+28 -12
View File
@@ -1,9 +1,11 @@
#include <xrpl/tx/transactors/oracle/OracleSet.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/OracleHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/InnerObjectFormats.h>
@@ -22,6 +24,7 @@
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <map>
#include <memory>
#include <set>
@@ -150,8 +153,9 @@ OracleSet::preclaim(PreclaimContext const& ctx)
if (!pairsDel.empty())
return tecTOKEN_PAIR_NOT_FOUND;
auto const oldCount = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? 2 : 1;
auto const newCount = pairs.size() > 5 ? 2 : 1;
auto const oldCount = calculateOracleReserve(sle);
auto const newCount = calculateOracleReserve(pairs);
adjustReserve = newCount - oldCount;
}
else
@@ -160,7 +164,7 @@ OracleSet::preclaim(PreclaimContext const& ctx)
if (!ctx.tx.isFieldPresent(sfProvider) || !ctx.tx.isFieldPresent(sfAssetClass))
return temMALFORMED;
adjustReserve = pairs.size() > 5 ? 2 : 1;
adjustReserve = calculateOracleReserve(pairs);
}
if (pairs.empty())
@@ -169,7 +173,7 @@ OracleSet::preclaim(PreclaimContext const& ctx)
return tecARRAY_TOO_LARGE;
auto const reserve =
ctx.view.fees().accountReserve(sleSetter->getFieldU32(sfOwnerCount) + adjustReserve);
accountReserve(ctx.view, sleSetter, ctx.j, {.ownerCountDelta = adjustReserve});
auto const& balance = sleSetter->getFieldAmount(sfBalance);
if (balance < reserve)
@@ -179,11 +183,22 @@ OracleSet::preclaim(PreclaimContext const& ctx)
}
static bool
adjustOwnerCount(ApplyContext& ctx, int count)
adjustOracleOwnerCount(ApplyContext& ctx, int count)
{
XRPL_ASSERT(std::abs(count) <= 2, "xrpl::adjustOracleOwnerCount abs(counter) <= 2");
if (auto const sleAccount = ctx.view().peek(keylet::account(ctx.tx[sfAccount])))
{
adjustOwnerCount(ctx.view(), sleAccount, count, ctx.journal);
if (count > 0)
{
increaseOwnerCount(
ctx.view(), sleAccount, {}, static_cast<std::uint32_t>(count), ctx.journal);
}
else if (count < 0)
{
decreaseOwnerCount(
ctx.view(), sleAccount, {}, static_cast<std::uint32_t>(-count), ctx.journal);
}
return true;
}
@@ -228,7 +243,7 @@ OracleSet::doApply()
priceData.setFieldCurrency(sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
pairs.emplace(tokenPairKey(entry), std::move(priceData));
}
auto const oldCount = pairs.size() > 5 ? 2 : 1;
auto const oldCount = calculateOracleReserve(pairs);
// update/add/delete pairs
for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
{
@@ -266,9 +281,10 @@ OracleSet::doApply()
(*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID];
}
auto const newCount = pairs.size() > 5 ? 2 : 1;
auto const adjust = newCount - oldCount;
if (adjust != 0 && !adjustOwnerCount(ctx_, adjust))
auto const newCount = calculateOracleReserve(pairs);
int32_t const adjust = newCount - oldCount;
if (adjust != 0 && !adjustOracleOwnerCount(ctx_, adjust))
return tefINTERNAL; // LCOV_EXCL_LINE
ctx_.view().update(sle);
@@ -317,8 +333,8 @@ OracleSet::doApply()
(*sle)[sfOwnerNode] = *page;
auto const count = series.size() > 5 ? 2 : 1;
if (!adjustOwnerCount(ctx_, count))
auto const count = calculateOracleReserve(series);
if (!adjustOracleOwnerCount(ctx_, count))
return tefINTERNAL; // LCOV_EXCL_LINE
ctx_.view().insert(sle);
@@ -7,13 +7,13 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
@@ -151,6 +151,7 @@ DepositPreauth::preclaim(PreclaimContext const& ctx)
TER
DepositPreauth::doApply()
{
auto applyViewContext = ctx_.getApplyViewContext();
if (ctx_.tx.isFieldPresent(sfAuthorize))
{
auto const sleOwner = view().peek(keylet::account(accountID_));
@@ -160,13 +161,10 @@ DepositPreauth::doApply()
// A preauth counts against the reserve of the issuing account, but we
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
{
STAmount const reserve{
view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
if (auto const ret = checkReserve(
applyViewContext, sleOwner, preFeeBalance_, {.ownerCountDelta = 1}, j_);
!isTesSuccess(ret))
return ret;
// Preclaim already verified that the Preauth entry does not yet exist.
// Create and populate the Preauth entry.
@@ -190,7 +188,8 @@ DepositPreauth::doApply()
slePreauth->setFieldU64(sfOwnerNode, *page);
// If we succeeded, the new entry counts against the creator's reserve.
adjustOwnerCount(view(), sleOwner, 1, j_);
increaseOwnerCount(applyViewContext, sleOwner, 1, j_);
addSponsorToLedgerEntry(applyViewContext, slePreauth);
}
else if (ctx_.tx.isFieldPresent(sfUnauthorize))
{
@@ -207,13 +206,10 @@ DepositPreauth::doApply()
// A preauth counts against the reserve of the issuing account, but we
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
{
STAmount const reserve{
view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
if (auto const ret = checkReserve(
applyViewContext, sleOwner, preFeeBalance_, {.ownerCountDelta = 1}, j_);
!isTesSuccess(ret))
return ret;
// Preclaim already verified that the Preauth entry does not yet exist.
// Create and populate the Preauth entry.
@@ -251,7 +247,8 @@ DepositPreauth::doApply()
slePreauth->setFieldU64(sfOwnerNode, *page);
// If we succeeded, the new entry counts against the creator's reserve.
adjustOwnerCount(view(), sleOwner, 1, j_);
increaseOwnerCount(applyViewContext, sleOwner, 1, j_);
addSponsorToLedgerEntry(applyViewContext, slePreauth);
}
else if (ctx_.tx.isFieldPresent(sfUnauthorizeCredentials))
{
@@ -289,8 +286,7 @@ DepositPreauth::removeFromLedger(ApplyView& view, uint256 const& preauthIndex, b
if (!sleOwner)
return tefINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleOwner, -1, j);
decreaseOwnerCountForObject(view, sleOwner, slePreauth, 1, j);
// Remove DepositPreauth from ledger.
view.erase(slePreauth);
+62 -15
View File
@@ -10,6 +10,7 @@
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/PermissionedDEXHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -37,6 +38,7 @@
#include <algorithm>
#include <cstdint>
#include <limits>
#include <memory>
#include <optional>
#include <unordered_set>
@@ -120,6 +122,21 @@ Payment::preflight(PreflightContext const& ctx)
if (!ctx.rules.enabled(featureMPTokensV1) && isDstMPT)
return temDISABLED;
if (tx.isFlag(tfSponsorCreatedAccount))
{
if (!ctx.rules.enabled(featureSponsor))
return temDISABLED;
if (tx.isFlag(tfNoRippleDirect) || tx.isFlag(tfPartialPayment) || tx.isFlag(tfLimitQuality))
return temINVALID_FLAG;
if (tx.isFieldPresent(sfSendMax) || tx.isFieldPresent(sfPaths))
return temINVALID;
if (!dstAmount.native())
return temBAD_AMOUNT;
}
if (!mpTokensV2 && isDstMPT && ctx.tx.isFieldPresent(sfPaths))
return temMALFORMED;
@@ -383,15 +400,28 @@ Payment::preclaim(PreclaimContext const& ctx)
{
// accountReserve is the minimum amount that an account can have.
// Reserve is not scaled by load.
JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist. "
<< "Insufficent payment to create account.";
if (!ctx.tx.isFlag(tfSponsorCreatedAccount))
{
// The minimum amount when creating a Sponsored Account is 1 drop.
// Since the reserve is covered by the sponsor, you don't need to hold the
// 1-increment reserve yourself.
JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist. "
<< "Insufficient payment to create account.";
// TODO: de-dupe
// Another transaction could create the account and then this
// transaction would succeed.
return tecNO_DST_INSUF_XRP;
// TODO: de-dupe
// Another transaction could create the account and then this
// transaction would succeed.
return tecNO_DST_INSUF_XRP;
}
}
}
else if (ctx.tx.isFlag(tfSponsorCreatedAccount))
{
// The tfSponsorCreatedAccount flag is specific to account creation via
// sponsorship. If the destination account already exists, applying this
// flag is invalid.
return tecNO_SPONSOR_PERMISSION;
}
else if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag))
{
// The tag is basically account-specific information we don't
@@ -470,6 +500,27 @@ Payment::doApply()
sleDst->setFieldU32(sfSequence, view().seq());
sleDst->setFieldAmount(sfBalance, XRPAmount(beast::kZero));
if (ctx_.tx.isFlag(tfSponsorCreatedAccount))
{
auto const sponsor = view().peek(keylet::account(accountID_));
if (!sponsor)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const currentSponsoringAccountCount =
sponsor->getFieldU32(sfSponsoringAccountCount);
if (currentSponsoringAccountCount == std::numeric_limits<std::uint32_t>::max())
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Sponsoring account count overflow for account "
<< to_string(accountID_);
return tecINTERNAL;
// LCOV_EXCL_STOP
}
sponsor->setFieldU32(sfSponsoringAccountCount, currentSponsoringAccountCount + 1);
addSponsorToLedgerEntry(sleDst, sponsor);
view().update(sponsor);
}
view().insert(sleDst);
}
else
@@ -636,16 +687,12 @@ Payment::doApply()
if (!sleSrc)
return tefINTERNAL; // LCOV_EXCL_LINE
// ownerCount is the number of entries in this ledger for this
// account that require a reserve.
auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
// the number of reserves in this ledger for this account that require a
// reserve.
auto const reserve = accountReserve(view(), sleSrc, j_);
// This is the total reserve in drops.
auto const reserve = view().fees().accountReserve(ownerCount);
// In a delegated payment, the fee payer is the delegated account,
// not the source account (accountID_).
bool const accountIsPayer = (ctx_.tx.getFeePayer() == accountID_);
// In a delegated / fee sponsored payment, the fee payer is not the source account (accountID_).
bool const accountIsPayer = ctx_.tx.getFeePayerID() == accountID_;
// preFeeBalance_ is the balance on the source account (accountID_) BEFORE the fees
// were charged. If source account is the fee payer, it must also cover the fee.
Loaded 100 of 170 files, more files were not shown because too many files have changed in this diff. Show more