Compare commits

..

232 Commits

Author SHA1 Message Date
TimothyBanks
03511bf0ec feat: Smart Contract codec poc 2026-07-10 12:00:14 -04:00
pwang200
8022fc33cf host function error path refactor (#7639) 2026-07-02 13:35:46 -04:00
Olek
9767d86de4 Memory transfer limit (#7000)
Count bytes copied across the boundaries (Wasm VM <-> Hostfunctions) and return error if limit reached (1 mb default)
2026-06-17 12:45:01 -04:00
Mayukha Vadari
ca2d999618 refactor: rename host functions (#7338)
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-06-12 15:53:12 -04:00
Mayukha Vadari
5ee903befc remove wasm engine tests 2026-06-09 17:09:45 -04:00
Olek
d582ae7990 HF one entry point (#7393)
Add one entry point for all HF for centralized exceptions handling, gas calculation and general checks.
Add exception handling for HF
Add FieldLocator object
Switch pointers to references for HF and runtime
Max size for parameters and sfData field is 1 kb now
Fix Allhf unittest, to provide correct locator
2026-06-03 21:53:12 -04:00
Olek
0dbe51c740 Cleanup and some refactoring (#7383) 2026-06-02 21:15:58 -04:00
Olek
63fff4b518 Fix HF tests (#7365) 2026-05-29 17:49:03 -04:00
Mayukha Vadari
d85bf722ea fix: Fix build issues post-clang-tidy changes (#7298) 2026-05-20 13:44:18 -04:00
Mayukha Vadari
b664989cfb fix clang-tidy issues 2026-05-19 15:11:55 -04:00
Mayukha Vadari
e77934302a Merge branch 'ripple/wasmi' of https://github.com/XRPLF/rippled into ripple/wasmi-host-functions 2026-05-19 15:10:21 -04:00
Mayukha Vadari
ef7aeca6bf Merge branch 'develop' into ripple/wasmi 2026-05-18 18:25:09 -04:00
Denis Angell
ad3d172a1f fix: Use account ledger entry when canceling token escrows (#6171) 2026-05-18 20:08:48 +00:00
Mayukha Vadari
ad7232cbc5 refactor: Rename account_ to accountID_ (#7284) 2026-05-18 10:56:54 +00:00
Michael Legleux
93836f22db ci: Add Linux package builds (DEB + RPM) to CI (#6639) 2026-05-16 05:08:37 +00:00
Mayukha Vadari
c7ecfc6a97 refactor: Clean up comments post-clang-tidy changes (#7283) 2026-05-15 23:02:04 +00:00
Mayukha Vadari
eec1d29b92 Merge branch 'develop' into ripple/wasmi 2026-05-15 11:36:56 -04:00
pwang200
971ba2281e clarify XLS-0102 host function stability rule (#7146) 2026-05-14 20:18:05 -04:00
pwang200
90357eeae1 bump get_nft host function cost from 1000 to 5000 (#7200) 2026-05-14 18:53:29 -04:00
Olek
597202a6f0 Refactoring float hostfunctions (#7053) 2026-05-07 12:33:22 -04:00
pwang200
1600b3e7f3 ai review nits fixes of host functions (#6963) 2026-04-30 13:56:55 -04:00
Mayukha Vadari
ecee732187 Merge branch 'develop' into ripple/wasmi 2026-04-22 17:22:28 -04:00
Olek
ce2586c039 Review fixes (#6512) 2026-04-20 14:03:39 -04:00
Olek
8cc2169939 test: Calling wrap functions from c++ side (#6699) 2026-04-09 18:48:58 -04:00
Mayukha Vadari
826f613ad8 Merge branch 'ripple/wasmi' of https://github.com/XRPLF/rippled into ripple/wasmi-host-functions 2026-04-08 13:51:09 -04:00
Mayukha Vadari
1259c1d5ca Merge branch 'develop' of https://github.com/XRPLF/rippled into ripple/wasmi 2026-04-08 13:48:41 -04:00
Olek
d2641d85bd New floats format, STAmount compatible (#6600) 2026-04-07 20:19:19 -04:00
Mayukha Vadari
75f66bd9fe fix build 2026-04-07 17:24:48 -04:00
Mayukha Vadari
7cd71cb659 Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2026-04-07 16:03:02 -04:00
Mayukha Vadari
9917f96166 Merge remote-tracking branch 'upstream/develop' into ripple/wasmi 2026-04-07 16:02:56 -04:00
Mayukha Vadari
e1cc82587b Merge branch 'ripple/wasmi' of https://github.com/XRPLF/rippled into ripple/wasmi-host-functions 2026-04-07 16:02:32 -04:00
Pratik Mankawde
2cc9439fde fix: Handle WSClient write failure when server closes WebSocket (#6671)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 16:01:26 -04:00
Ayaz Salikhov
52af9582e2 ci: Change conditions for uploading artifacts in public/private/org repos (#6734) 2026-04-07 16:01:26 -04:00
Bart
46e88dc732 refactor: Rename non-functional uses of ripple(d) to xrpl(d) (#6676)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-04-07 16:01:26 -04:00
Mayukha Vadari
bc24f2e211 refactor: Move more helper files into libxrpl/ledger/helpers (#6731)
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-04-07 16:01:26 -04:00
Mayukha Vadari
7a7c993b15 fix: Minor RPC fixes (#6730) 2026-04-07 16:01:26 -04:00
Zhiyuan Wang
9733ca8f91 fix: Prevent deletion of MPTokens with active escrow (#6635)
Co-authored-by: Bart <bthomee@users.noreply.github.com>
2026-04-07 16:01:26 -04:00
Vito Tumas
18d5e3e226 fix: Clamp VaultClawback to assetsAvailable for zero-amount clawback (#6646) 2026-04-07 16:01:25 -04:00
Vito Tumas
b30b4e1d65 fix: Add assorted Lending Protocol fixes (#6678)
Co-authored-by: Shawn Xie <35279399+shawnxie999@users.noreply.github.com>
2026-04-07 16:01:25 -04:00
Mayukha Vadari
d435893602 fix: Change variable signedness and correctly handle std::optional (#6657) 2026-04-07 16:01:25 -04:00
Olek
00b0cf50f6 Update wasmi to 1.0.9 (#6727) 2026-04-07 15:58:29 -04:00
Mayukha Vadari
7ef256499c Merge branch 'ripple/wasmi' of https://github.com/XRPLF/rippled into wasmi-host-functions 2026-04-03 09:57:07 -04:00
Mayukha Vadari
1338062be7 Merge branch 'develop' of https://github.com/XRPLF/rippled into ripple/wasmi 2026-04-03 09:56:55 -04:00
Mayukha Vadari
4fc1778ec8 fix clang-tidy issues 2026-04-03 09:56:42 -04:00
Oleksandr
65322d9e78 fix Clang-tidy 2026-04-02 21:33:43 -04:00
Mayukha Vadari
c5598a4284 fix clang-tidy issues 2026-04-02 19:05:34 -04:00
Mayukha Vadari
0deb6bcadf fix build 2026-04-02 18:39:14 -04:00
Mayukha Vadari
9b013b559b Merge branch 'ripple/wasmi' of https://github.com/XRPLF/rippled into wasmi-host-functions 2026-04-02 17:54:32 -04:00
Mayukha Vadari
1d4a3c00b8 Merge branch 'develop' of https://github.com/XRPLF/rippled into ripple/wasmi 2026-04-02 17:53:53 -04:00
Mayukha Vadari
4b34102e8e test: Use proper length limits in codecov_tests (#6626) 2026-03-25 09:10:12 -07:00
Olek
d006433579 Base divison of large fixtures (#6637) 2026-03-25 09:42:33 -04:00
Mayukha Vadari
a7ab8ee923 clang-tidy fixes 2026-03-24 10:22:01 -07:00
Mayukha Vadari
e0073a4402 Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2026-03-24 10:20:43 -07:00
Mayukha Vadari
2930ef217f Merge remote-tracking branch 'upstream/develop' into ripple/wasmi 2026-03-24 10:20:38 -07:00
Mayukha Vadari
9dbb301699 more clang-tidy fixes 2026-03-24 10:20:06 -07:00
Mayukha Vadari
531e8b6ebd fix clang-tidy 2026-03-24 09:46:01 -07:00
Mayukha Vadari
90397e1a52 more build fixes 2026-03-24 09:41:08 -07:00
Mayukha Vadari
888ca2e6d9 fix build 2026-03-24 09:29:05 -07:00
Mayukha Vadari
b6514b680f Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2026-03-24 08:47:12 -07:00
Mayukha Vadari
913e4b919e Merge remote-tracking branch 'upstream/develop' into ripple/wasmi 2026-03-24 08:41:12 -07:00
Olek
196e6a1b27 Clang-format fixtures.cpp (#6610) 2026-03-20 14:26:26 -04:00
Olek
27468ddbcf Add import / export sections test (#6497) 2026-03-19 12:46:58 -04:00
Mayukha Vadari
bce5d91e45 Merge branch 'develop' into ripple/wasmi 2026-03-12 14:37:01 -04:00
Mayukha Vadari
654338fa66 Merge branch 'develop' into ripple/wasmi 2026-03-06 16:27:50 -04:00
Mayukha Vadari
9c25d18851 Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2026-03-05 13:48:33 -04:00
Mayukha Vadari
3a825a41e1 Merge branch 'develop' into ripple/wasmi 2026-03-05 13:48:16 -04:00
Jingchen
a9ebf786c6 Modularise wasm (#6441)
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2026-03-04 20:21:51 +00:00
Olek
5afe8cc321 Fix clang tidy (#6463)
* Fix clang tidy

* Add exponent overflow test
2026-03-04 11:30:33 -05:00
Mayukha Vadari
bc5ec3c962 assorted fixes (#6376) 2026-03-04 09:30:09 -04:00
Mayukha Vadari
1775251e90 Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2026-03-03 11:18:41 -04:00
Mayukha Vadari
61bcb7621f Merge branch 'develop' into ripple/wasmi 2026-03-03 11:18:26 -04:00
Mayukha Vadari
a3f71b1774 Merge branch 'develop' into ripple/wasmi 2026-03-02 17:06:17 -05:00
Mayukha Vadari
4df7d1a4bb rename variable 2026-03-02 16:48:02 -04:00
Mayukha Vadari
125df7a425 Merge remote-tracking branch 'upstream/ripple/wasmi' into wasmi-host-functions 2026-02-27 16:46:43 -05:00
Mayukha Vadari
b08bcf5d21 Merge branch 'develop' into ripple/wasmi 2026-02-27 16:41:44 -05:00
Mayukha Vadari
dc413aef0c Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2026-02-27 16:28:34 -05:00
Mayukha Vadari
77dfd56ace Merge branch 'develop' into ripple/wasmi 2026-02-27 13:49:25 -05:00
Olek
953b9a3500 Disable reusing wasm module (#6364)
* Remove ability to re-use wasm module

* Check that HFS object is always new

* Fix clang format

* Remove perf tests

* temp build fix

* Fix merge
2026-02-26 15:30:46 -05:00
Olek
1d9ec84350 Test invalid opcodes (#6392) 2026-02-26 09:59:30 -05:00
Olek
0392846a17 UT for wasm parameters (#6413) 2026-02-25 11:49:27 -05:00
Mayukha Vadari
1b4a564369 fix build issues 2026-02-18 13:20:29 -05:00
Mayukha Vadari
fd524c4be9 fix pre-commit 2026-02-18 12:41:56 -05:00
Mayukha Vadari
495dda7f58 Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2026-02-18 12:36:07 -05:00
Mayukha Vadari
9c3c0280b1 Merge branch 'develop' into ripple/wasmi 2026-02-18 12:35:51 -05:00
Mayukha Vadari
f73d8a6cf2 clean up some hf code (#6354)
* clean up some hf code

* fix comments

* fix ubsan

* Revert "fix ubsan"
2026-02-13 11:27:50 -05:00
Olek
6728ab52b7 Add tests for wasm functions with many parameters (#6343)
* Add functions with many parameters

* Add 10k locals function

* Module with  5k functions

* fix typo

Co-authored-by: Mayukha Vadari <mvadari@gmail.com>

---------

Co-authored-by: Mayukha Vadari <mvadari@gmail.com>
2026-02-10 18:10:33 -05:00
Mayukha Vadari
77673663ca fix cspell issues in tests (#6348) 2026-02-10 17:42:41 -05:00
Mayukha Vadari
c1381f8ddd Merge branch 'ripple/wasmi' into wasmi-host-functions 2026-02-10 17:27:18 -05:00
Mayukha Vadari
bd16f7989d Merge branch 'develop' into ripple/wasmi 2026-02-10 17:26:33 -05:00
Mayukha Vadari
65f9cf80c0 add readme to src/xrpld/app/wasm (#6340)
* add readme to src/xrpl/app/wasm

* important block

* respond to copilot
2026-02-09 12:13:39 -05:00
Mayukha Vadari
de55a5ebfc Merge branch 'ripple/wasmi' into wasmi-host-functions 2026-02-04 18:13:15 -05:00
Mayukha Vadari
2ec4a1114e Merge branch 'develop' into ripple/wasmi 2026-02-04 18:13:00 -05:00
Olek
ba03a8a9d2 Fix negation of int64_t (#6296) 2026-02-03 17:43:54 -05:00
Mayukha Vadari
7c8279ec83 use buffers for uint32 WASM params (#6291) 2026-02-03 16:08:46 -05:00
Mayukha Vadari
0418ffb26a Merge branch 'ripple/wasmi' into wasmi-host-functions 2026-02-03 14:52:16 -05:00
Mayukha Vadari
b2627039f6 Merge branch 'develop' into ripple/wasmi 2026-02-03 14:51:59 -05:00
Mayukha Vadari
8f97ec3bde Merge branch 'ripple/wasmi' into wasmi-host-functions 2026-01-29 13:54:30 -05:00
Mayukha Vadari
e85e7b1b1a Merge branch 'develop' into ripple/wasmi 2026-01-29 13:53:55 -05:00
Mayukha Vadari
803a344c65 fix clang-format 2026-01-28 16:35:02 -05:00
Mayukha Vadari
4eb34f381a Merge branch 'ripple/wasmi' into wasmi-host-functions 2026-01-28 15:56:40 -05:00
Mayukha Vadari
72fffb6e51 Merge branch 'develop' into ripple/wasmi 2026-01-28 15:56:18 -05:00
Mayukha Vadari
f7ee580f01 Merge commit '5f638f55536def0d88b970d1018a465a238e55f4' into ripple/wasmi 2026-01-28 15:56:11 -05:00
Mayukha Vadari
122d405750 Merge commit '92046785d1fea5f9efe5a770d636792ea6cab78b' into ripple/wasmi 2026-01-28 15:56:04 -05:00
Olek
c1c1b4ea67 Reject non-canonical binaries (#6277)
* Reject non-canonical binaries

* Review fixes

* Cleanup Number2 class

* Use enum instead of 0
2026-01-27 16:30:51 -05:00
Mayukha Vadari
977caea0a5 Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2026-01-27 13:26:55 -05:00
Mayukha Vadari
d7ed6d6512 Merge branch 'develop' into ripple/wasmi 2026-01-27 13:26:39 -05:00
Olek
f1f2e2629f Fix for Big-Endian machines (#6245) 2026-01-27 13:05:54 -05:00
Olek
917c610f96 Ensure request size less than int limit (#6239)
* Ensure request size less than int limit

* Move size check to wasmParams function
2026-01-27 12:37:47 -05:00
Mayukha Vadari
317e533d81 clean up Wasm_test.cpp more (#6278) 2026-01-26 15:21:15 -05:00
Olek
4160677878 Switch to series expansion method for ln() (#6268)
* Switch to series expansion method for ln()
Add float lg() tests to Number tests;
* Rename lg -> log10
* Add check for 0 to log10()
2026-01-26 14:04:03 -05:00
Olek
df98db1452 Check wasm return type (#6240)
* Check wasm return type

* Add more tests
2026-01-23 16:12:14 -05:00
Mayukha Vadari
673476ef1b Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2026-01-23 13:13:26 -05:00
Mayukha Vadari
8bc6f9cd70 Merge branch 'develop' into ripple/wasmi 2026-01-23 13:13:11 -05:00
Mayukha Vadari
ba5debfecd update return calculation (#6250) 2026-01-22 17:01:56 -05:00
Mayukha Vadari
f4a27c9b6d minor refactor of Wasm_test (#6229) 2026-01-21 18:05:48 -05:00
Olek
fd1cb318e3 Check that max parameters length is multiple of sizeof(int32) (#6253) 2026-01-21 17:22:47 -05:00
Mayukha Vadari
8c3544a58c Merge branch 'ripple/wasmi' into wasmi-host-functions 2026-01-21 12:57:47 -05:00
Mayukha Vadari
ed5139d4e3 Merge branch 'develop' into ripple/wasmi 2026-01-21 12:57:29 -05:00
Olek
42494dd4cf Ensure lifetime of imports (#6230) 2026-01-21 12:43:12 -05:00
Mayukha Vadari
ce84cc8b44 improve trace hf code (#6190)
* adjust trace statements

* add helper function

* use lambda instead

* use same paradigm in TestHostFunctions

* oops
2026-01-15 20:50:55 -05:00
Mayukha Vadari
9a9a7aab01 Add Vector256 support to the locator (#6131)
* add Vector256 nesting/length support

* [WIP] add tests

* fix tests

* simplify with helper function

* oops typo

* remove static variable

* respond to comments

* STBaseOrUInt256->FieldValue

* oops

* add more tests for coverage

* respond to comments
2026-01-15 20:14:42 -05:00
Olek
209a1a6ffa Don't throw from hostfunctions stack (#6221) 2026-01-15 19:52:22 -05:00
Oleksandr
fc35a9f9c8 Fix usage of the Number class 2026-01-14 19:36:50 -05:00
Oleksandr
c5e50aa221 Fix merge issues 2026-01-14 14:46:35 -05:00
Mayukha Vadari
074b1f00d5 Merge branch 'ripple/wasmi' into wasmi-host-functions 2026-01-14 13:04:28 -05:00
Mayukha Vadari
7a9d245950 Merge branch 'develop' into ripple/wasmi 2026-01-14 13:01:35 -05:00
Mayukha Vadari
1809fe07f2 remove test file 2026-01-14 12:43:12 -05:00
Mayukha Vadari
409c67494a move helper functions to separate file (#6178)
* move helper functions to separate file

* break it up into sections, split out float helpers

* split impls into multiple cpp files

* namespace detail

* fix build issue

* fix tests

* clean up

* put float helpers into wasm_float namespace
2026-01-13 20:34:57 -05:00
Olek
c626b6403a Fix unaligned access (#6208) 2026-01-13 16:40:42 -05:00
Olek
81cbc91927 Fix traces (#6127)
* Fix traces
* More tests for codecov
* Review fixes
* trace float test
* Fix return value for traces
* Remove SuiteJournalSink2
* Add explicit severity
* Move logs to ApplyView
* Add check for output strings
* Merging fix
2026-01-13 16:38:48 -05:00
pwang200
1c812a6c4d disable Wasm features added in Wasmi 1.0, and fix unit test fuel cost due to Wasmi 1.0 fuel changes (#6173)
* disable 4 more wasm features

* unit tests for disabled Wasmi 1.0 features

* fix unit tests failed due to fuel changes

* rearrange wasm feature unit tests

* fix gas costs

* Update src/test/app/wasm_fixtures/wat/custom_page_sizes.wat

---------

Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
2026-01-12 22:04:33 -05:00
Mayukha Vadari
0724927799 Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2026-01-12 15:17:36 -05:00
Olek
d83ec96848 Switch to wasmi v1.0.6 (#6204) 2026-01-12 13:36:02 -05:00
Mayukha Vadari
375dd50b35 Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2026-01-12 13:19:17 -05:00
Mayukha Vadari
419d53ec4c Merge branch 'develop' into ripple/wasmi 2026-01-12 13:10:58 -05:00
Mayukha Vadari
d4d70d5675 Merge branch 'develop' into ripple/wasmi 2026-01-12 12:27:48 -05:00
Olek
6ab15f8377 Add checks to allocate (#6185) 2026-01-09 14:49:09 -05:00
pwang200
91f3d51f3d fix start function loop 2026-01-09 11:38:54 -05:00
pwang200
9ed60b45f8 section corruption unit tests 2026-01-08 16:15:36 -05:00
pwang200
d5c53dcfd2 fix Uninitialized import entries lead to undefined behavior During WASM Instantiation 2026-01-08 16:14:49 -05:00
Mayukha Vadari
e94321fb41 Merge branch 'ripple/wasmi' into wasmi-host-functions 2026-01-08 11:44:15 -05:00
Mayukha Vadari
bbc28b3b1c Merge branch 'develop' into ripple/wasmi 2026-01-08 11:42:28 -05:00
Mayukha Vadari
843e981c8a Merge remote-tracking branch 'upstream/ripple/wasmi' into wasmi-host-functions 2026-01-07 16:52:56 -05:00
Mayukha Vadari
5aab274b7a Merge branch 'develop' into ripple/wasmi 2026-01-07 16:52:10 -05:00
Mayukha Vadari
2c30e41191 use the develop hashes 2026-01-07 16:50:45 -05:00
Mayukha Vadari
8ea5106b0b Merge branch 'develop' into ripple/wasmi 2026-01-07 14:34:49 -05:00
Mayukha Vadari
f57f67a8ae infinite loop test (#6064) 2026-01-07 11:51:58 -05:00
pwang200
a98269f049 a batch of memory, table, and trap tests (#6100)
wasm memory, table, and trap unit tests
2026-01-06 14:03:18 -05:00
Mayukha Vadari
b66bc47ca9 fix more merge issues 2026-01-06 13:30:30 -05:00
Mayukha Vadari
0e9c7458bb fix more merge issues 2026-01-05 18:53:14 -05:00
Mayukha Vadari
1d89940653 merge fixes 2026-01-05 18:48:09 -05:00
Mayukha Vadari
1a1a6806ec Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2026-01-05 18:44:41 -05:00
Mayukha Vadari
1977df9c2e Merge remote-tracking branch 'upstream/develop' into ripple/wasmi 2026-01-05 18:43:49 -05:00
Mayukha Vadari
6c95548df5 Merge remote-tracking branch 'upstream/develop' into ripple/wasmi 2025-12-22 15:51:19 -08:00
Olek
69ab39d658 Fix potential memory leaks found by srlabs (#6145) 2025-12-18 14:13:48 -05:00
Mayukha Vadari
b9eb66eecc fix parameter index desynchronization (#6148) 2025-12-17 14:19:34 -08:00
Mayukha Vadari
881087dd3d Merge remote-tracking branch 'upstream/ripple/wasmi' into wasmi-host-functions 2025-12-08 14:29:47 -05:00
Mayukha Vadari
90e0bbd0fc Merge branch 'develop' into ripple/wasmi 2025-12-08 14:28:41 -05:00
Olek
b57df290de Use conan repo for wasmi lib (#6109)
* Use conan repo for wasmi lib
* Generate lockfile
2025-12-08 13:02:01 -05:00
Mayukha Vadari
8a403f1241 Merge branch 'develop' into ripple/wasmi 2025-12-05 14:32:48 -05:00
Mayukha Vadari
6d2640871d Merge branch 'develop' into ripple/wasmi 2025-12-02 18:40:54 -05:00
pwang200
c145598ff9 add memory limit and disable float and other advanced instructions 2025-12-02 00:09:20 -05:00
Olek
50e5608d86 wasmi HF cost 2025-12-01 20:21:52 -05:00
Mayukha Vadari
7a7b96107c Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2025-11-25 03:42:05 +05:30
Olek
500bb68831 Fix win build (#6076) 2025-11-24 16:56:23 -05:00
Mayukha Vadari
53eb0f60bc fix another build issue 2025-11-25 03:10:58 +05:30
Mayukha Vadari
41205ae928 Merge branch 'ripple/wasmi' into wasmi-host-functions 2025-11-25 03:01:51 +05:30
Mayukha Vadari
c33b0ae463 fix build issue 2025-11-25 02:58:57 +05:30
Mayukha Vadari
16087c9680 fix merge issue 2025-11-25 02:57:47 +05:30
Mayukha Vadari
56bc6d58f6 Merge branch 'ripple/wasmi' into wasmi-host-functions 2025-11-25 02:45:00 +05:30
Mayukha Vadari
ef5d335e09 update 2025-11-25 02:44:18 +05:30
Mayukha Vadari
25c3060fef remove conan.lock (temporary) 2025-11-25 02:40:57 +05:30
Mayukha Vadari
ce9f0b38a4 Merge branch 'develop' into ripple/wasmi 2025-11-25 02:33:47 +05:30
Mayukha Vadari
35f7cbf772 update 2025-11-25 02:31:51 +05:30
Mayukha Vadari
0db564d261 WASMI data 2025-11-04 15:57:07 -05:00
Mayukha Vadari
427b7ea104 run rename script 2025-11-04 15:29:08 -05:00
Mayukha Vadari
7bf6878b4b fix imports 2025-11-04 14:49:45 -05:00
Mayukha Vadari
0bc1a115ff Merge branch 'wamr' into wamr-host-functions 2025-11-04 13:36:22 -05:00
Mayukha Vadari
334bcfa5ef Merge branch 'develop' into wamr 2025-11-04 13:36:01 -05:00
Mayukha Vadari
106dea4559 update fixtures to use the latest version of stdlib 2025-11-04 13:35:25 -05:00
Mayukha Vadari
3ffdcf8114 allow 0-value trace amounts 2025-11-04 13:19:40 -05:00
Olek
4021a7eb28 Wamr and HF security review fixes (#5965) 2025-10-31 10:34:31 -04:00
Ayaz Salikhov
0690fda0f1 Merge branch 'develop' into ripple/wamr 2025-10-30 14:12:15 +00:00
Mayukha Vadari
d0cc48c6d3 Update cmake/RippledCore.cmake
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2025-10-29 16:41:11 -04:00
Olek
d66e3c949e Chores: Sort package list (#5963) 2025-10-29 12:55:07 -04:00
Mayukha Vadari
0c65a386b5 fix tests 2025-10-24 18:01:01 -04:00
Mayukha Vadari
29f5430881 fix bug 2025-10-24 16:05:38 -04:00
Mayukha Vadari
101f285bcd return size from updateData 2025-10-24 16:01:45 -04:00
Mayukha Vadari
286dc6322b Merge branch 'ripple/wamr' into ripple/wamr-host-functions 2025-10-23 15:38:28 -04:00
Mayukha Vadari
c9346cd40d Merge branch 'develop' into ripple/wamr 2025-10-23 15:38:04 -04:00
Mayukha Vadari
1c5683ec78 Merge branch 'ripple/wamr' into ripple/wamr-host-functions 2025-10-20 11:53:22 -04:00
Mayukha Vadari
9bee155d59 Merge branch 'develop' into ripple/wamr 2025-10-20 11:53:03 -04:00
Mayukha Vadari
f34b05f4de Merge branch 'ripple/wamr' into ripple/wamr-host-functions 2025-10-16 12:12:05 -04:00
Mayukha Vadari
97ce25f4ce Merge branch 'develop' into ripple/wamr 2025-10-16 12:11:55 -04:00
Olek
9e14c14a26 Use xrplf conan repo for wamr (#5862) 2025-10-13 15:11:21 -04:00
Mayukha Vadari
c507880d8f Merge branch 'ripple/wamr' into ripple/wamr-host-functions 2025-10-13 13:57:22 -04:00
Mayukha Vadari
3f8328bbf8 Merge branch 'develop' into ripple/wamr 2025-10-13 13:55:07 -04:00
Mayukha Vadari
c10a5f9ef6 Merge branch 'ripple/wamr' into ripple/wamr-host-functions 2025-10-09 17:10:31 -04:00
Mayukha Vadari
3c141de695 Merge branch 'develop' into ripple/wamr 2025-10-09 16:52:25 -04:00
Mayukha Vadari
da2b9455f2 fix: remove get_ledger_account_hash and get_ledger_tx_hash host functions (#5850)
* remove `get_ledger_account_hash` and `get_ledger_tx_hash`

* fix build+tests
2025-10-06 16:38:40 -04:00
Mayukha Vadari
cb622488c0 Merge branch 'ripple/wamr' into ripple/wamr-host-functions 2025-10-02 14:35:25 -04:00
Mayukha Vadari
32f971fec6 Merge branch 'develop' into ripple/wamr 2025-10-02 14:35:13 -04:00
Mayukha Vadari
8dea76baa4 Merge branch 'ripple/wamr' into ripple/wamr-host-functions 2025-09-30 14:42:49 -04:00
Mayukha Vadari
299fbe04c4 Merge branch 'develop' into ripple/wamr 2025-09-30 14:42:24 -04:00
Mayukha Vadari
57fc1df7d7 switch from wasm32-unknown-unknown to wasm32v1-none (#5814) 2025-09-29 15:43:22 -04:00
Mayukha Vadari
eaba76f9e6 Merge branch 'ripple/wamr' into ripple/wamr-host-functions 2025-09-26 16:37:25 -04:00
Mayukha Vadari
cb702cc238 Merge branch 'develop' into ripple/wamr 2025-09-26 16:37:04 -04:00
Mayukha Vadari
b69b4a0a4a Merge branch 'ripple/wamr' into ripple/wamr-host-functions 2025-09-26 15:51:48 -04:00
Mayukha Vadari
50d6072a73 Merge branch 'develop' into ripple/wamr 2025-09-26 15:51:40 -04:00
Olek
d24cd50e61 Switch to own wamr fork (#5808) 2025-09-23 16:39:21 -04:00
Mayukha Vadari
9f5875158c Merge branch 'ripple/wamr' into ripple/wamr-host-functions 2025-09-22 18:23:45 -04:00
Mayukha Vadari
c3dc33c861 Merge branch 'develop' into ripple/wamr 2025-09-22 18:23:35 -04:00
Olek
6be8f2124c Latests HF perf test (#5789) 2025-09-18 15:51:39 -04:00
Mayukha Vadari
edfed06001 fix merge issues 2025-09-18 15:39:49 -04:00
Mayukha Vadari
1c646dba91 Merge remote-tracking branch 'upstream/ripple/wamr' into wamr-host-functions 2025-09-18 15:29:02 -04:00
Mayukha Vadari
6781068058 Merge branch 'develop' into ripple/wamr 2025-09-18 15:27:54 -04:00
Mayukha Vadari
cfe57c1dfe Merge branch 'ripple/wamr' into ripple/wamr-host-functions 2025-09-18 14:37:58 -04:00
Mayukha Vadari
c34d09a971 Merge branch 'develop' into ripple/wamr 2025-09-18 14:24:34 -04:00
Mayukha Vadari
ebd90c4742 chore: remove unneeded float stuff (#5729) 2025-09-11 18:41:24 -04:00
Mayukha Vadari
ba52d34828 test: improve codecov in HostFuncWrapper.cpp (#5730) 2025-09-11 18:09:08 -04:00
Mayukha Vadari
1b6312afb3 rearrange files 2025-09-11 16:34:03 -04:00
Mayukha Vadari
bf32dc2e72 add fixtures files 2025-09-11 16:28:11 -04:00
Mayukha Vadari
a15d65f7a2 update tests 2025-09-11 16:20:33 -04:00
Mayukha Vadari
2de8488855 add temBAD_WASM 2025-09-11 16:02:17 -04:00
Mayukha Vadari
129aa4bfaa bring out IOUAmount.h 2025-09-11 13:18:42 -04:00
Mayukha Vadari
b1d70db63b limits 2025-09-10 15:05:06 -04:00
Mayukha Vadari
f03c3aafe4 misc host function files 2025-09-10 15:02:48 -04:00
Mayukha Vadari
51a9f106d1 CODEOWNERS 2025-09-10 14:59:09 -04:00
Mayukha Vadari
bfc048e3fe add tests 2025-09-10 14:57:23 -04:00
Mayukha Vadari
83418644f7 add host functions 2025-09-10 14:56:21 -04:00
Mayukha Vadari
dbc9dd5bfc Add WAMR integration code 2025-09-10 14:56:08 -04:00
Mayukha Vadari
45ab15d4b5 add WAMR dependency 2025-09-10 14:40:48 -04:00
196 changed files with 21984 additions and 2623 deletions

View File

@@ -32,7 +32,32 @@ We will further set additional CMake arguments as follows:
"""
def generate_strategy_matrix(all: bool, config: Config) -> list:
def build_config_name(os_entry: dict[str, str], platform: str, build_type: str) -> str:
parts = [os_entry["distro_name"]]
for key in ("distro_version", "compiler_name", "compiler_version"):
if value := os_entry[key]:
parts.append(value)
parts.append("arm64" if "arm64" in platform else "amd64")
parts.append(build_type.lower())
return "-".join(parts)
def generate_packaging_matrix(config: Config) -> list[dict]:
"""Emit one entry per os entry with `package: true`. Architecture is
hardcoded to linux/amd64 here (and the runner is hardcoded at the
workflow level) until arm64 packaging is ready.
"""
return [
{
"artifact_name": f"xrpld-{build_config_name(os, 'linux/amd64', 'Release')}",
"os": os,
}
for os in config.os
if os.get("package", False)
]
def generate_strategy_matrix(all: bool, config: Config) -> list[dict]:
configurations = []
for architecture, os, build_type, cmake_args in itertools.product(
config.architecture, config.os, config.build_type, config.cmake_args
@@ -101,14 +126,15 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
continue
# RHEL:
# - 9 using GCC 12: Debug on linux/amd64.
# - 9 using GCC 12: Debug and Release on linux/amd64
# (Release is required for RPM packaging).
# - 10 using Clang: Release on linux/amd64.
if os["distro_name"] == "rhel":
skip = True
if os["distro_version"] == "9":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
and build_type == "Debug"
and build_type in ["Debug", "Release"]
and architecture["platform"] == "linux/amd64"
):
skip = False
@@ -123,7 +149,8 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
continue
# Ubuntu:
# - Jammy using GCC 12: Debug on linux/arm64.
# - Jammy using GCC 12: Debug on linux/arm64, Release on
# linux/amd64 (Release is required for DEB packaging).
# - Noble using GCC 14: Release on linux/amd64.
# - Noble using Clang 18: Debug on linux/amd64.
# - Noble using Clang 19: Release on linux/arm64.
@@ -136,6 +163,12 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
and architecture["platform"] == "linux/arm64"
):
skip = False
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
and build_type == "Release"
and architecture["platform"] == "linux/amd64"
):
skip = False
elif os["distro_version"] == "noble":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-14"
@@ -218,17 +251,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
# Generate a unique name for the configuration, e.g. macos-arm64-debug
# or debian-bookworm-gcc-12-amd64-release.
config_name = os["distro_name"]
if (n := os["distro_version"]) != "":
config_name += f"-{n}"
if (n := os["compiler_name"]) != "":
config_name += f"-{n}"
if (n := os["compiler_version"]) != "":
config_name += f"-{n}"
config_name += (
f"-{architecture['platform'][architecture['platform'].find('/')+1:]}"
)
config_name += f"-{build_type.lower()}"
config_name = build_config_name(os, architecture["platform"], build_type)
if "-Dcoverage=ON" in cmake_args:
config_name += "-coverage"
if "-Dunity=ON" in cmake_args:
@@ -332,10 +355,19 @@ if __name__ == "__main__":
required=False,
type=Path,
)
parser.add_argument(
"-p",
"--packaging",
help="Emit the packaging matrix (derived from the 'package' field on os entries) instead of the build/test matrix.",
action="store_true",
)
args = parser.parse_args()
matrix = []
if args.config is None or args.config == "":
if args.packaging:
config_path = args.config if args.config else THIS_DIR / "linux.json"
matrix += generate_packaging_matrix(read_config(config_path))
elif args.config is None or args.config == "":
matrix += generate_strategy_matrix(
args.all, read_config(THIS_DIR / "linux.json")
)

View File

@@ -127,7 +127,8 @@
"distro_version": "9",
"compiler_name": "gcc",
"compiler_version": "12",
"image_sha": "4c086b9"
"image_sha": "4c086b9",
"package": true
},
{
"distro_name": "rhel",
@@ -169,7 +170,8 @@
"distro_version": "jammy",
"compiler_name": "gcc",
"compiler_version": "12",
"image_sha": "4c086b9"
"image_sha": "4c086b9",
"package": true
},
{
"distro_name": "ubuntu",

View File

@@ -64,11 +64,13 @@ jobs:
.github/workflows/reusable-build-test-config.yml
.github/workflows/reusable-build-test.yml
.github/workflows/reusable-clang-tidy.yml
.github/workflows/reusable-package.yml
.github/workflows/reusable-strategy-matrix.yml
.github/workflows/reusable-test.yml
.github/workflows/reusable-upload-recipe.yml
.clang-tidy
.codecov.yml
cfg/**
cmake/**
conan/**
external/**
@@ -78,6 +80,10 @@ jobs:
CMakeLists.txt
conanfile.py
conan.lock
LICENSE.md
package/**
README.md
- name: Check whether to run
# This step determines whether the rest of the workflow should
# run. The rest of the workflow will run if this job runs AND at
@@ -134,6 +140,11 @@ jobs:
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
package:
needs: [should-run, build-test]
if: ${{ needs.should-run.outputs.go == 'true' }}
uses: ./.github/workflows/reusable-package.yml
upload-recipe:
needs:
- should-run
@@ -168,6 +179,7 @@ jobs:
- check-rename
- clang-tidy
- build-test
- package
- upload-recipe
- notify-clio
runs-on: ubuntu-latest

View File

@@ -1,5 +1,5 @@
# This workflow uploads the libxrpl recipe to the Conan remote when a versioned
# tag is pushed.
# This workflow uploads the libxrpl recipe to the Conan remote and builds
# release packages when a versioned tag is pushed.
name: Tag
on:
@@ -22,3 +22,22 @@ jobs:
secrets:
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
build-test:
if: ${{ github.repository == 'XRPLF/rippled' }}
uses: ./.github/workflows/reusable-build-test.yml
strategy:
fail-fast: true
matrix:
os: [linux]
with:
ccache_enabled: false
os: ${{ matrix.os }}
strategy_matrix: minimal
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
package:
if: ${{ github.repository == 'XRPLF/rippled' }}
needs: build-test
uses: ./.github/workflows/reusable-package.yml

View File

@@ -21,11 +21,13 @@ on:
- ".github/workflows/reusable-build-test-config.yml"
- ".github/workflows/reusable-build-test.yml"
- ".github/workflows/reusable-clang-tidy.yml"
- ".github/workflows/reusable-package.yml"
- ".github/workflows/reusable-strategy-matrix.yml"
- ".github/workflows/reusable-test.yml"
- ".github/workflows/reusable-upload-recipe.yml"
- ".clang-tidy"
- ".codecov.yml"
- "cfg/**"
- "cmake/**"
- "conan/**"
- "external/**"
@@ -35,6 +37,9 @@ on:
- "CMakeLists.txt"
- "conanfile.py"
- "conan.lock"
- "LICENSE.md"
- "package/**"
- "README.md"
# Run at 06:32 UTC on every day of the week from Monday through Friday. This
# will force all dependencies to be rebuilt, which is useful to verify that
@@ -95,3 +100,7 @@ jobs:
secrets:
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
package:
needs: build-test
uses: ./.github/workflows/reusable-package.yml

99
.github/workflows/reusable-package.yml vendored Normal file
View File

@@ -0,0 +1,99 @@
# Build Linux packages (DEB and RPM) from pre-built binary artifacts.
# Discovers which configurations to package from linux.json (os entries
# with "package": true) and fans out one job per entry. Today only
# linux/amd64 is emitted; the architecture is hardcoded both here
# (runner) and in generate.py.
name: Package
on:
workflow_call:
inputs:
pkg_release:
description: "Package release number. Increment when repackaging the same executable."
required: false
type: string
default: "1"
defaults:
run:
shell: bash
env:
BUILD_DIR: build
jobs:
generate-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.generate.outputs.matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: 3.13
- name: Generate packaging matrix
id: generate
working-directory: .github/scripts/strategy-matrix
run: |
./generate.py --packaging --config=linux.json >> "${GITHUB_OUTPUT}"
generate-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
sparse-checkout: |
.github/actions/generate-version
src/libxrpl/protocol/BuildInfo.cpp
- name: Generate version
id: version
uses: ./.github/actions/generate-version
package:
needs: [generate-matrix, generate-version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
name: "${{ matrix.artifact_name }}"
permissions:
contents: read
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
container: ${{ format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) }}
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download pre-built binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ matrix.artifact_name }}
path: ${{ env.BUILD_DIR }}
- name: Make binary executable
run: chmod +x "${BUILD_DIR}/xrpld"
- name: Build package
env:
PKG_VERSION: ${{ needs.generate-version.outputs.version }}
PKG_RELEASE: ${{ inputs.pkg_release }}
run: ./package/build_pkg.sh
- name: Upload package artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: ${{ github.event.repository.visibility == 'public' }}
with:
name: ${{ matrix.artifact_name }}-pkg-${{ needs.generate-version.outputs.version }}
path: |
${{ env.BUILD_DIR }}/debbuild/*.deb
${{ env.BUILD_DIR }}/debbuild/*.ddeb
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/*.rpm
if-no-files-found: error

View File

@@ -93,6 +93,7 @@ find_package(OpenSSL REQUIRED)
find_package(secp256k1 REQUIRED)
find_package(SOCI REQUIRED)
find_package(SQLite3 REQUIRED)
find_package(wasmi REQUIRED)
find_package(xxHash REQUIRED)
target_link_libraries(
@@ -134,6 +135,7 @@ endif()
include(XrplCore)
include(XrplProtocolAutogen)
include(XrplInstall)
include(XrplPackaging)
include(XrplValidatorKeys)
if(tests)

View File

@@ -28,7 +28,7 @@
# https://vl.ripple.com
# https://unl.xrplf.org
# http://127.0.0.1:8000
# file:///etc/opt/xrpld/vl.txt
# file:///etc/xrpld/vl.txt
#
# [validator_list_keys]
#

View File

@@ -67,6 +67,7 @@ target_link_libraries(
Xrpl::opts
Xrpl::syslibs
secp256k1::secp256k1
wasmi::wasmi
xrpl.libpb
xxHash::xxhash
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>

44
cmake/XrplPackaging.cmake Normal file
View File

@@ -0,0 +1,44 @@
#[===================================================================[
Linux packaging support: 'package' target.
The packaging script (package/build_pkg.sh) installs to FHS-standard
paths (/usr/bin, /etc/xrpld, etc.) regardless of CMAKE_INSTALL_PREFIX,
so no prefix guard is needed here.
#]===================================================================]
if(NOT is_linux)
message(STATUS "Packaging not supported on non-Linux hosts")
return()
endif()
if(NOT DEFINED pkg_release)
set(pkg_release 1)
endif()
find_program(RPMBUILD_EXECUTABLE rpmbuild)
find_program(DPKG_BUILDPACKAGE_EXECUTABLE dpkg-buildpackage)
if(NOT (RPMBUILD_EXECUTABLE OR DPKG_BUILDPACKAGE_EXECUTABLE))
message(
STATUS
"Neither rpmbuild nor dpkg-buildpackage found; 'package' target not available"
)
return()
endif()
set(package_env
SRC_DIR=${CMAKE_SOURCE_DIR}
BUILD_DIR=${CMAKE_BINARY_DIR}
PKG_VERSION=${xrpld_version}
PKG_RELEASE=${pkg_release}
)
add_custom_target(
package
COMMAND
${CMAKE_COMMAND} -E env ${package_env}
${CMAKE_SOURCE_DIR}/package/build_pkg.sh
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DEPENDS xrpld
COMMENT "Building Linux package (deb/rpm inferred from host tooling)"
VERBATIM
)

View File

@@ -3,6 +3,8 @@
"requires": [
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1777558780.503",
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
"wasmi/1.0.9#1fecdab9b90c96698eb35ea99ca4f5cb%1772227278.324",
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1776096494.149",
"soci/4.0.3#fe32b9ad5eb47e79ab9e45a68f363945%1774450067.231",
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878",

View File

@@ -34,6 +34,7 @@ class Xrpl(ConanFile):
"openssl/3.6.2",
"secp256k1/0.7.1",
"soci/4.0.3",
"wasmi/1.0.9",
"zlib/1.3.2",
]
@@ -214,6 +215,7 @@ class Xrpl(ConanFile):
"soci::soci",
"secp256k1::secp256k1",
"sqlite3::sqlite",
"wasmi::wasmi",
"xxhash::xxhash",
"zlib::zlib",
]

View File

@@ -7,6 +7,7 @@ ignorePaths:
- cmake/**
- LICENSE.md
- .clang-tidy
- src/test/app/wasm_fixtures/*.c
language: en
allowCompoundWords: true # TODO (#6334)
ignoreRandomStrings: true
@@ -65,6 +66,7 @@ words:
- Btrfs
- Buildx
- canonicality
- cdylib
- changespq
- checkme
- choco
@@ -99,12 +101,15 @@ words:
- desync
- desynced
- determ
- disablerepo
- distro
- doxyfile
- dxrpl
- enabled
- enablerepo
- endmacro
- exceptioned
- EXPECT_STREQ
- Falco
- fcontext
- finalizers
@@ -162,6 +167,7 @@ words:
- Merkle
- Metafuncton
- misprediction
- missingok
- mptbalance
- MPTDEX
- mptflags
@@ -193,7 +199,9 @@ words:
- NOLINT
- NOLINTNEXTLINE
- nonxrp
- noreplace
- noripple
- notifempty
- nudb
- nullptr
- nunl
@@ -213,6 +221,7 @@ words:
- preauthorize
- preauthorizes
- preclaim
- preun
- protobuf
- protos
- ptrs
@@ -247,17 +256,20 @@ words:
- sfields
- shamap
- shamapitem
- shlibs
- sidechain
- SIGGOOD
- sle
- sles
- soci
- socidb
- SRPMS
- sslws
- statsd
- STATSDCOLLECTOR
- stissue
- stnum
- stnumber
- stobj
- stobject
- stpath
@@ -280,8 +292,8 @@ words:
- txn
- txns
- txs
- UBSAN
- ubsan
- UBSAN
- umant
- unacquired
- unambiguity
@@ -317,8 +329,8 @@ words:
- wthread
- xbridge
- xchain
- xfloat
- ximinez
- EXPECT_STREQ
- XMACRO
- xrpkuwait
- xrpl

View File

@@ -19,11 +19,6 @@
namespace xrpl {
// Forward declarations for SLE wrappers
template <typename ViewT>
class AccountRoot;
using RAccountRoot = AccountRoot<ReadView>;
enum class SkipEntry : bool { No = false, Yes };
//------------------------------------------------------------------------------
@@ -162,7 +157,7 @@ canWithdraw(
ReadView const& view,
AccountID const& from,
AccountID const& to,
RAccountRoot const& toWrapped,
SLE::const_ref toSle,
STAmount const& amount,
bool hasDestinationTag);

View File

@@ -2,10 +2,8 @@
#include <xrpl/basics/Expected.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -17,160 +15,46 @@
namespace xrpl {
/**
* View-parameterized wrapper for AccountRoot ledger entries.
*
* AccountRoot<ReadView> — read-only access to account data
* AccountRoot<ApplyView> — read-write access, with insert/update/erase
* and domain-specific write methods
/** Check if the issuer has the global freeze flag set.
@param issuer The account to check
@return true if the account has global freeze set
*/
[[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.
[[nodiscard]] XRPAmount
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j);
/** Adjust the owner count up or down. */
void
adjustOwnerCount(
ApplyView& view,
std::shared_ptr<SLE> const& sle,
std::int32_t amount,
beast::Journal j);
/** Returns IOU issuer transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
* is represented as 1,010,000,000.
* @param issuer The IOU issuer
*/
template <typename ViewT>
class AccountRoot : public SLEBase<ViewT>
{
static constexpr bool kIsWritable = SLEBase<ViewT>::kIsWritable;
AccountID const id_;
public:
/** Constructor for read-only context */
AccountRoot(
AccountID const& id,
ReadView const& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires(!kIsWritable)
: SLEBase<ViewT>(view.read(keylet::account(id)), view, j), id_(id)
{
}
/** Constructor for writable context */
AccountRoot(
AccountID const& id,
ApplyView& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: SLEBase<ViewT>(keylet::account(id), view, j), id_(id)
{
}
/** Converting constructor: writable → read-only. */
template <WritableView OtherViewT>
AccountRoot(AccountRoot<OtherViewT> const& other)
requires(!kIsWritable)
: SLEBase<ViewT>(other), id_(other.id())
{
}
/** Create an AccountRoot backed by a brand-new SLE.
*/
[[nodiscard]] static AccountRoot
makeNew(
AccountID const& id,
ApplyView& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
{
return AccountRoot(id, view, j, std::make_shared<SLE>(keylet::account(id)));
}
[[nodiscard]] AccountID const&
id() const
{
return id_;
}
// --- Read-only domain methods (available on both specializations) ---
/** Check if the issuer has the global freeze flag set.
@return true if the account has global freeze set
*/
[[nodiscard]] bool
isGlobalFrozen() const;
/** Returns IOU issuer transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
* is represented as 1,010,000,000.
*/
[[nodiscard]] Rate
transferRate() const;
// 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.
[[nodiscard]] XRPAmount
xrpLiquid(std::int32_t ownerCountAdj) const;
/** Checks the destination and tag.
- Checks that the SLE is not null.
- If the SLE requires a destination tag, checks that there is a tag.
*/
[[nodiscard]] TER
checkDestinationAndTag(bool hasDestinationTag) const;
/** Returns true if and only if sleAcct is a pseudo-account or specific
pseudo-accounts in pseudoFieldFilter.
Returns false if sleAcct is:
- NOT a pseudo-account OR
- NOT a ltACCOUNT_ROOT OR
- null pointer
*/
[[nodiscard]] bool
isPseudoAccount(std::set<SField const*> const& pseudoFieldFilter = {}) const;
[[nodiscard]] bool
operator==(AccountRoot const& other) const
{
return id_ == other.id_;
}
[[nodiscard]] bool
operator==(AccountID const& other) const
{
return id_ == other;
}
// --- Write-only domain methods (compile-time gated) ---
/** Adjust the owner count up or down. */
void
adjustOwnerCount(std::int32_t amount)
requires kIsWritable;
private:
// Private constructor only used by `makeNew`
AccountRoot(AccountID const& id, ApplyView& view, beast::Journal j, std::shared_ptr<SLE> sle)
requires kIsWritable
: SLEBase<ViewT>(std::move(sle), view, j), id_(id)
{
this->insert();
}
};
// CTAD deduction guide — bare AccountRoot(id, view) always deduces read-only.
// For writable access, use WAccountRoot(id, applyView) explicitly.
AccountRoot(AccountID const&, ReadView const&) -> AccountRoot<ReadView>;
AccountRoot(AccountID const&, ReadView const&, beast::Journal) -> AccountRoot<ReadView>;
// Backward-compatible aliases
using RAccountRoot = AccountRoot<ReadView>;
using WAccountRoot = AccountRoot<ApplyView>;
// Explicit instantiation declarations (definitions in .cpp)
extern template class AccountRoot<ReadView>;
extern template class AccountRoot<ApplyView>;
[[nodiscard]] Rate
transferRate(ReadView const& view, AccountID const& issuer);
/** Generate a pseudo-account address from a pseudo owner key.
@param pseudoOwnerKey The key to generate the address from
@return The generated account ID
*/
[[nodiscard]] AccountID
AccountID
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey);
/** Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account
@@ -183,6 +67,29 @@ pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey);
[[nodiscard]] std::vector<SField const*> const&
getPseudoAccountFields();
/** Returns true if and only if sleAcct is a pseudo-account or specific
pseudo-accounts in pseudoFieldFilter.
Returns false if sleAcct is:
- NOT a pseudo-account OR
- NOT a ltACCOUNT_ROOT OR
- null pointer
*/
[[nodiscard]] bool
isPseudoAccount(
std::shared_ptr<SLE const> sleAcct,
std::set<SField const*> const& pseudoFieldFilter = {});
/** Convenience overload that reads the account from the view. */
[[nodiscard]] inline bool
isPseudoAccount(
ReadView const& view,
AccountID const& accountId,
std::set<SField const*> const& pseudoFieldFilter = {})
{
return isPseudoAccount(view.read(keylet::account(accountId)), pseudoFieldFilter);
}
/**
* Create pseudo-account, storing pseudoOwnerKey into ownerField.
*
@@ -194,4 +101,12 @@ getPseudoAccountFields();
[[nodiscard]] Expected<std::shared_ptr<SLE>, TER>
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField);
/** Checks the destination and tag.
- Checks that the SLE is not null.
- If the SLE requires a destination tag, checks that there is a tag.
*/
[[nodiscard]] TER
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag);
} // namespace xrpl

View File

@@ -5,7 +5,6 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STTx.h>
@@ -70,7 +69,8 @@ verifyDepositPreauth(
STTx const& tx,
ApplyView& view,
AccountID const& src,
RAccountRoot const& dst,
AccountID const& dst,
std::shared_ptr<SLE const> const& sleDst,
beast::Journal j);
} // namespace xrpl

View File

@@ -11,8 +11,6 @@
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/Rate.h>
#include <variant>
namespace xrpl {
template <ValidIssueType T>
@@ -20,7 +18,7 @@ TER
escrowUnlockApplyHelper(
ApplyView& view,
Rate lockedRate,
std::variant<std::shared_ptr<SLE>, WAccountRoot> dest,
std::shared_ptr<SLE> const& sleDest,
STAmount const& xrpBalance,
STAmount const& amount,
AccountID const& issuer,
@@ -34,7 +32,7 @@ inline TER
escrowUnlockApplyHelper<Issue>(
ApplyView& view,
Rate lockedRate,
std::variant<std::shared_ptr<SLE>, WAccountRoot> dest,
std::shared_ptr<SLE> const& sleDest,
STAmount const& xrpBalance,
STAmount const& amount,
AccountID const& issuer,
@@ -57,14 +55,8 @@ escrowUnlockApplyHelper<Issue>(
if (!view.exists(trustLineKey) && createAsset)
{
// For backwards compatibility: if dest is not WAccountRoot, return error
if (!std::holds_alternative<WAccountRoot>(dest))
return tefEXCEPTION;
auto& wrappedDest = std::get<WAccountRoot>(dest);
// Can the account cover the trust line's reserve?
if (std::uint32_t const ownerCount = {wrappedDest->at(sfOwnerCount)};
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
xrpBalance < view.fees().accountReserve(ownerCount + 1))
{
JLOG(journal.trace()) << "Trust line does not exist. "
@@ -77,28 +69,28 @@ escrowUnlockApplyHelper<Issue>(
STAmount initialBalance(issue);
initialBalance.get<Issue>().account = noAccount();
if (TER const ter = WIOUIssuance(view, issue, journal)
.trustCreate(
recvLow, // is dest low?
issuer, // source
receiver, // destination
trustLineKey.key, // ledger index
wrappedDest, // Account to add to
false, // authorize account
!wrappedDest->isFlag(lsfDefaultRipple),
false, // freeze trust line
false, // deep freeze trust line
initialBalance, // zero initial balance
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
journal); // journal
if (TER const ter = trustCreate(
view, // payment sandbox
recvLow, // is dest low?
issuer, // source
receiver, // destination
trustLineKey.key, // ledger index
sleDest, // Account to add to
false, // authorize account
!sleDest->isFlag(lsfDefaultRipple), //
false, // freeze trust line
false, // deep freeze trust line
initialBalance, // zero initial balance
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
journal); // journal
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
wrappedDest.update();
view.update(sleDest);
}
if (!view.exists(trustLineKey) && !receiverIssuer)
@@ -170,7 +162,7 @@ inline TER
escrowUnlockApplyHelper<MPTIssue>(
ApplyView& view,
Rate lockedRate,
std::variant<std::shared_ptr<SLE>, WAccountRoot> dest,
std::shared_ptr<SLE> const& sleDest,
STAmount const& xrpBalance,
STAmount const& amount,
AccountID const& issuer,
@@ -186,26 +178,19 @@ escrowUnlockApplyHelper<MPTIssue>(
auto const issuanceKey = keylet::mptIssuance(mptID);
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset && !receiverIssuer)
{
// For backwards compatibility: if dest is not WAccountRoot, return error
if (!std::holds_alternative<WAccountRoot>(dest))
return tefEXCEPTION;
auto& wrappedDest = std::get<WAccountRoot>(dest);
if (std::uint32_t const ownerCount = {wrappedDest->at(sfOwnerCount)};
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
xrpBalance < view.fees().accountReserve(ownerCount + 1))
{
return tecINSUFFICIENT_RESERVE;
}
if (auto const ter = WMPTokenIssuance(view, mptID, journal).createMPToken(receiver, 0);
!isTesSuccess(ter))
if (auto const ter = createMPToken(view, mptID, receiver, 0); !isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
// update owner count.
wrappedDest.adjustOwnerCount(1);
adjustOwnerCount(view, sleDest, 1, journal);
}
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && !receiverIssuer)
@@ -233,13 +218,13 @@ escrowUnlockApplyHelper<MPTIssue>(
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
return WMPTokenIssuance(view, amount.get<MPTIssue>(), journal)
.unlockEscrow(
sender,
receiver,
finalAmt,
view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
journal);
return unlockEscrowMPT(
view,
sender,
receiver,
finalAmt,
view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
journal);
}
} // namespace xrpl

View File

@@ -487,15 +487,4 @@ loanMakePayment(
LoanPaymentType const paymentType,
beast::Journal j);
// LoanBroker-specific `adjustOwnerCount` function (temporary, while the Wrapped classes are WIP)
// Assert will check the type, so that we ensure it's not used by anything else
// Order of parameters is different from the old `adjustOwnerCount` function to avoid anything
// accidentally calling this with the wrong type.
void
adjustOwnerCount(
std::shared_ptr<SLE> const& sle,
ApplyView& view,
std::int32_t amount,
beast::Journal j);
} // namespace xrpl

View File

@@ -3,297 +3,117 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEBase.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <initializer_list>
#include <memory>
#include <optional>
namespace xrpl {
//------------------------------------------------------------------------------
//
// MPTokenIssuance ledger entry wrapper (view-parameterized)
// Freeze checking (MPT-specific)
//
//------------------------------------------------------------------------------
/**
* View-parameterized wrapper for MPTokenIssuance ledger entries.
*
* MPTokenIssuance<ReadView> — read-only access to issuance data
* MPTokenIssuance<ApplyView> — read-write access, with insert/update/erase
* and domain-specific write methods
*
* Carries the MPTID and MPTIssue alongside the SLE so callers don't need to
* thread them through every method call.
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
[[nodiscard]] bool
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
[[nodiscard]] bool
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth = 0);
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
MPTIssue const& mptIssue,
int depth = 0);
//------------------------------------------------------------------------------
//
// Transfer rate (MPT-specific)
//
//------------------------------------------------------------------------------
/** Returns MPT transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
* is represented as 1,010,000,000.
* @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object
*/
template <typename ViewT>
class MPTokenIssuance : public SLEBase<ViewT>, public TokenBase<ViewT>
{
static constexpr bool kIsWritable = SLEBase<ViewT>::kIsWritable;
[[nodiscard]] Rate
transferRate(ReadView const& view, MPTID const& issuanceID);
MPTID const mptID_;
MPTIssue const mptIssue_;
//------------------------------------------------------------------------------
//
// Holding checks (MPT-specific)
//
//------------------------------------------------------------------------------
public:
/** Constructor for read-only context (MPTIssue) */
MPTokenIssuance(
ReadView const& view,
MPTIssue const& mptIssue,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires(!kIsWritable)
: SLEBase<ViewT>(view.read(keylet::mptIssuance(mptIssue.getMptID())), view, j)
, mptID_(mptIssue.getMptID())
, mptIssue_(mptIssue)
{
}
[[nodiscard]] TER
canAddHolding(ReadView const& view, MPTIssue const& mptIssue);
/** Constructor for read-only context (MPTID) */
MPTokenIssuance(
ReadView const& view,
MPTID const& mptID,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires(!kIsWritable)
: SLEBase<ViewT>(view.read(keylet::mptIssuance(mptID)), view, j)
, mptID_(mptID)
, mptIssue_(MPTIssue(mptID))
{
}
//------------------------------------------------------------------------------
//
// Authorization (MPT-specific)
//
//------------------------------------------------------------------------------
/** Constructor for writable context (MPTIssue) */
MPTokenIssuance(
ApplyView& view,
MPTIssue const& mptIssue,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: SLEBase<ViewT>(keylet::mptIssuance(mptIssue.getMptID()), view, j)
, mptID_(mptIssue.getMptID())
, mptIssue_(mptIssue)
{
}
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
beast::Journal journal,
std::uint32_t flags = 0,
std::optional<AccountID> holderID = std::nullopt);
/** Constructor for writable context (MPTID) */
MPTokenIssuance(
ApplyView& view,
MPTID const& mptID,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: SLEBase<ViewT>(keylet::mptIssuance(mptID), view, j)
, mptID_(mptID)
, mptIssue_(MPTIssue(mptID))
{
}
/** Check if the account lacks required authorization for MPT.
*
* requireAuth check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
* WeakAuth intentionally allows missing MPTokens under MPToken V2.
*/
[[nodiscard]] TER
requireAuth(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& account,
AuthType authType = AuthType::Legacy,
int depth = 0);
/** Converting constructor: writable → read-only. */
template <WritableView OtherViewT>
MPTokenIssuance(MPTokenIssuance<OtherViewT> const& other)
requires(!kIsWritable)
: SLEBase<ViewT>(other), mptID_(other.getMptID()), mptIssue_(other.getMptIssue())
{
}
/** Enforce account has MPToken to match its authorization.
*
* Called from doApply - it will check for expired (and delete if found any)
* credentials matching DomainID set in MPTokenIssuance. Must be called if
* requireAuth(...MPTIssue...) returned tesSUCCESS or tecEXPIRED in preclaim.
*/
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance,
beast::Journal j);
/** Create an MPTokenIssuance backed by a brand-new SLE (already inserted into
* the view).
*/
[[nodiscard]] static MPTokenIssuance
makeNew(
MPTID const& mptID,
ApplyView& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
{
return MPTokenIssuance(mptID, view, j, std::make_shared<SLE>(keylet::mptIssuance(mptID)));
}
[[nodiscard]] static MPTokenIssuance
makeNew(
std::uint32_t const seq,
AccountID const& issuer,
ApplyView& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
{
auto const mptID = makeMptID(seq, issuer);
return MPTokenIssuance(mptID, view, j, std::make_shared<SLE>(keylet::mptIssuance(mptID)));
}
// --- Inline accessors ---
[[nodiscard]] MPTID const&
getMptID() const
{
return mptID_;
}
[[nodiscard]] MPTIssue const&
getMptIssue() const
{
return mptIssue_;
}
[[nodiscard]] AccountID const&
getIssuer() const
{
return mptIssue_.getIssuer();
}
// --- Read-only domain methods (available on both specializations) ---
[[nodiscard]] bool
isGlobalFrozen() const override;
[[nodiscard]] bool
isIndividualFrozen(AccountID const& account) const override;
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to kMaxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] bool
isFrozen(AccountID const& account, int depth = 0) const override;
[[nodiscard]] bool
isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth = 0) const;
/** Returns MPT transfer fee as Rate. */
[[nodiscard]] Rate
transferRate() const override;
[[nodiscard]] TER
canAddHolding() const;
/** Check if the account lacks required authorization for MPT. */
[[nodiscard]] TER
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
const override;
/** Check if `from` is allowed to send MPT to `to`. */
[[nodiscard]] TER
canTransfer(AccountID const& from, AccountID const& to) const override;
/** Check if the token requires authorization for holders. */
[[nodiscard]] bool
requiresAuth() const override;
/** Check if the token issuer has enabled clawback capability. */
[[nodiscard]] bool
canClawback() const override;
// --- Write-only domain methods (compile-time gated) ---
[[nodiscard]] TER
addEmptyHolding(AccountID const& accountID, XRPAmount priorBalance, beast::Journal journal)
requires kIsWritable;
[[nodiscard]] TER
removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
requires kIsWritable;
[[nodiscard]] TER
authorizeMPToken(
XRPAmount const& priorBalance,
AccountID const& account,
beast::Journal journal,
std::uint32_t flags = 0,
std::optional<AccountID> holderID = std::nullopt)
requires kIsWritable;
/** Enforce account has MPToken to match its authorization.
*
* Called from doApply - it will check for expired (and delete if found any)
* credentials matching DomainID set in MPTokenIssuance. Must be called if
* requireAuth(...) returned tesSUCCESS or tecEXPIRED in preclaim.
*/
[[nodiscard]] TER
enforceMPTokenAuthorization(
AccountID const& account,
XRPAmount const& priorBalance,
beast::Journal j)
requires kIsWritable;
// --- Amount accessors ---
/** Maximum amount allowed for this issuance (sfMaximumAmount or default). */
[[nodiscard]] std::int64_t
maxAmount() const;
/** Available amount = maxAmount - OutstandingAmount.
* Throws if the issuance SLE does not exist.
*/
[[nodiscard]] std::int64_t
availableAmount() const;
/** Funds available for the issuer to sell in an issuer-owned offer. */
[[nodiscard]] STAmount
issuerFundsToSelfIssue() const;
// --- Holder MPToken management (writable) ---
/** Create a holder-side MPToken for `account` under this issuance. */
[[nodiscard]] TER
createMPToken(AccountID const& account, std::uint32_t flags)
requires kIsWritable;
/** Ensure `holder` has an MPToken under this issuance, creating one if needed. */
[[nodiscard]] TER
checkCreateMPT(AccountID const& holder, beast::Journal j)
requires kIsWritable;
/** Notify the view that the issuer self-debited `amount`. */
void
issuerSelfDebitHook(std::uint64_t amount)
requires kIsWritable;
/** Lock MPT for escrow from `sender`. The amount must match this issuance. */
[[nodiscard]] TER
lockEscrow(AccountID const& sender, STAmount const& amount, beast::Journal j)
requires kIsWritable;
/** Unlock previously-escrowed MPT, transferring `netAmount` to `receiver`,
* with `grossAmount - netAmount` accounted as a transfer fee against
* OutstandingAmount.
*/
[[nodiscard]] TER
unlockEscrow(
AccountID const& sender,
AccountID const& receiver,
STAmount const& netAmount,
STAmount const& grossAmount,
beast::Journal j)
requires kIsWritable;
private:
// Private constructor only used by `makeNew`.
MPTokenIssuance(MPTID const& mptID, ApplyView& view, beast::Journal j, std::shared_ptr<SLE> sle)
requires kIsWritable
: SLEBase<ViewT>(std::move(sle), view, j), mptID_(mptID), mptIssue_(MPTIssue(mptID))
{
this->insert();
}
};
// CTAD deduction guides — bare MPTokenIssuance(view, ...) always deduces read-only.
// For writable access, use WMPTokenIssuance(view, ...) explicitly.
MPTokenIssuance(ReadView const&, MPTIssue const&) -> MPTokenIssuance<ReadView>;
MPTokenIssuance(ReadView const&, MPTIssue const&, beast::Journal) -> MPTokenIssuance<ReadView>;
MPTokenIssuance(ReadView const&, MPTID const&) -> MPTokenIssuance<ReadView>;
MPTokenIssuance(ReadView const&, MPTID const&, beast::Journal) -> MPTokenIssuance<ReadView>;
// Backward-compatible aliases
using RMPTokenIssuance = MPTokenIssuance<ReadView>;
using WMPTokenIssuance = MPTokenIssuance<ApplyView>;
// Explicit instantiation declarations (definitions in .cpp)
extern template class MPTokenIssuance<ReadView>;
extern template class MPTokenIssuance<ApplyView>;
/** Check if the destination account is allowed
* to receive MPT. Return tecNO_AUTH if it doesn't
* and tesSUCCESS otherwise.
*/
[[nodiscard]] TER
canTransfer(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& from,
AccountID const& to);
/** Check if Asset can be traded on DEX. return tecNO_PERMISSION
* if it doesn't and tesSUCCESS otherwise.
@@ -301,6 +121,63 @@ extern template class MPTokenIssuance<ApplyView>;
[[nodiscard]] TER
canTrade(ReadView const& view, Asset const& asset);
//------------------------------------------------------------------------------
//
// Empty holding operations (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
beast::Journal journal);
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal);
//------------------------------------------------------------------------------
//
// Escrow operations (MPT-specific)
//
//------------------------------------------------------------------------------
TER
lockEscrowMPT(
ApplyView& view,
AccountID const& uGrantorID,
STAmount const& saAmount,
beast::Journal j);
TER
unlockEscrowMPT(
ApplyView& view,
AccountID const& uGrantorID,
AccountID const& uGranteeID,
STAmount const& netAmount,
STAmount const& grossAmount,
beast::Journal j);
TER
createMPToken(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
std::uint32_t const flags);
TER
checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
beast::Journal j);
//------------------------------------------------------------------------------
//
// MPT Overflow related
@@ -316,6 +193,9 @@ maxMPTAmount(SLE const& sleIssuance);
std::int64_t
availableMPTAmount(SLE const& sleIssuance);
std::int64_t
availableMPTAmount(ReadView const& view, MPTID const& mptID);
/** Checks for two types of OutstandingAmount overflow during a send operation.
* 1. **Direct directSendNoFee (Overflow: No):** A true overflow check when
* `OutstandingAmount > MaximumAmount`. This threshold is used for direct
@@ -331,15 +211,21 @@ isMPTOverflow(
std::int64_t maximumAmount,
AllowMPTOverflow allowOverflow);
/** Delete AMMs MPToken. The passed `sle` must be obtained from a prior
* call to view.peek().
/**
* Determine funds available for an issuer to sell in an issuer owned offer.
* Issuing step, which could be either MPTEndPointStep last step or BookStep's
* TakerPays may overflow OutstandingAmount. Redeeming step, in BookStep's
* TakerGets redeems the offer's owner funds, essentially balancing out
* the overflow, unless the offer's owner is the issuer.
*/
[[nodiscard]] TER
deleteAMMMPToken(
ApplyView& view,
std::shared_ptr<SLE> sleMPT,
AccountID const& ammAccountID,
beast::Journal j);
[[nodiscard]] STAmount
issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue);
/** Facilitate tracking of MPT sold by an issuer owning MPT sell offer.
* See ApplyView::issuerSelfDebitHookMPT().
*/
void
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount);
//------------------------------------------------------------------------------
//

View File

@@ -1,53 +0,0 @@
# Ledger Entry Helpers (`helpers/`)
## Overview
This folder contains helper classes and free functions for working with **Serialized Ledger Entries (SLEs)**. Its centerpiece is `SLEBase.h`, which defines the template class `SLEBase<ViewT>` — a type-safe, context-aware wrapper around the raw `std::shared_ptr<SLE>` used throughout the rest of the codebase.
## The Problem: Untyped SLE Access
Historically, ledger entries are passed around as bare `std::shared_ptr<SLE>` (or `std::shared_ptr<SLE const>`). This has several drawbacks:
1. **No compile-time entry-type safety.** Any code holding an `std::shared_ptr<SLE>` can read or write _any_ field on _any_ ledger entry type. Nothing prevents you from calling `sle->getFieldU32(sfOwnerCount)` on an Offer SLE, even though Offers don't have that field.
2. **No read/write distinction.** A function that only needs to _read_ an entry still receives a mutable `std::shared_ptr<SLE>`, making it easy to accidentally mutate state. Conversely, a function that _must_ write has no way to express that requirement in its signature.
3. **No association with the view.** The SLE and the `ReadView` / `ApplyView` it came from travel as separate arguments, so callers must manually keep them in sync and remember to call `view.update(sle)` after mutations.
## The Solution: `SLEBase.h`
`SLEBase.h` introduces a single template class `SLEBase<ViewT>` that pairs an SLE with its view context and enforces read/write semantics at compile time via `requires` clauses.
**`SLEBase<ReadView>`** holds a `std::shared_ptr<SLE const>` and a `ReadView const&`. Write-only members are excluded at compile time.
holds a mutable `std::shared_ptr<SLE>`, an `ApplyView&`, and a `Keylet`. It exposes `insert()`, `update()`, `erase()`, and `newSLE()` to keep the SLE and its view in sync automatically.
A converting constructor allows implicit conversion from `SLEBase<ApplyView>` to `SLEBase<ReadView>`, so functions taking a read-only wrapper can accept a writable one without a cast.
### Template Pattern
Each entry type is a single template class parameterized on the view type:
```
AccountRoot<ReadView> — read-only: RAccountRoot
AccountRoot<ApplyView> — writable: WAccountRoot
```
Both specializations share all domain read methods. Write methods on `WAccountRoot` are gated with `requires is_writable` so they are unavailable on `RAccountRoot` at compile time. The `R`/`W` prefix aliases (`RAccountRoot`, `WAccountRoot`) are provided for convenience and backward compatibility.
## Files in This Directory
| File | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SLEBase.h` | Template base class `SLEBase<ViewT>` for read-only and writable SLE wrappers |
| `AccountRootHelpers.h` | `AccountRoot<ViewT>` wrapper (`RAccountRoot`, `WAccountRoot`) and free functions for pseudo-accounts |
| `CredentialHelpers.h` | Free functions for Credential ledger entries |
| `DirectoryHelpers.h` | Free functions for directory traversal (`dirFirst`, `dirNext`, `forEachItem`, etc.) |
| `MPTokenHelpers.h` | Free functions for MPToken ledger entries and MPTokenIssuance<ViewT> wrapper (`RMPTokenIssuance`, `WMPTokenIssuance`) functions for MPTokenIssuance objects |
| `OfferHelpers.h` | Free function `offerDelete` for removing Offer entries |
| `RippleStateHelpers.h` | Free functions for RippleState (trust line) entries: credit, freeze, issuance, authorization |
| `TokenHelpers.h` | Shared token helpers (freeze/auth checks used by both IOU and MPT paths) |
| `VaultHelpers.h` | Free functions for Vault ledger entries |
## Migration Status
This migration is still in progress. New code should prefer the wrapper style where possible; existing free functions will be migrated incrementally.

View File

@@ -3,14 +3,12 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
//------------------------------------------------------------------------------
//
@@ -22,218 +20,137 @@ namespace xrpl {
//------------------------------------------------------------------------------
//
// IOUIssuance ledger entry wrapper (view-parameterized)
// Credit functions (from Credit.h)
//
//------------------------------------------------------------------------------
/**
* View-parameterized wrapper for an IOU issuance.
*
* Because IOUs have no dedicated ledger entry — issuance properties live on
* the issuer's AccountRoot — IOUIssuance<V> inherits from AccountRoot<V> and
* adds the currency context plus IOU-specific accessors (RequireAuth /
* AllowTrustLineClawback flags, freeze checks against the holder's trust
* line, etc.).
*
* IOUIssuance<ReadView> — read-only
* IOUIssuance<ApplyView> — read-write
*/
template <typename ViewT>
class IOUIssuance : public AccountRoot<ViewT>, public TokenBase<ViewT>
/** Calculate the maximum amount of IOUs that an account can hold
@param view the ledger to check against.
@param account the account of interest.
@param issuer the issuer of the IOU.
@param currency the IOU to check.
@return The maximum amount that can be held.
*/
/** @{ */
STAmount
creditLimit(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency);
IOUAmount
creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur);
/** @} */
/** Returns the amount of IOUs issued by issuer that are held by an account
@param view the ledger to check against.
@param account the account of interest.
@param issuer the issuer of the IOU.
@param currency the IOU to check.
*/
/** @{ */
STAmount
creditBalance(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency);
/** @} */
//------------------------------------------------------------------------------
//
// Freeze checking (IOU-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isIndividualFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
[[nodiscard]] inline bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
static constexpr bool kIsWritable = SLEBase<ViewT>::kIsWritable;
return isIndividualFrozen(view, account, issue.currency, issue.account);
}
Currency const currency_;
[[nodiscard]] bool
isFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
public:
/** Constructor for read-only context (Issue). */
IOUIssuance(
ReadView const& view,
Issue const& issue,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires(!kIsWritable)
: AccountRoot<ViewT>(issue.getIssuer(), view, j), currency_(issue.currency)
{
}
[[nodiscard]] inline bool
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isFrozen(view, account, issue.currency, issue.account);
}
/** Constructor for read-only context (issuer + currency). */
IOUIssuance(
ReadView const& view,
AccountID const& issuer,
Currency const& currency,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires(!kIsWritable)
: AccountRoot<ViewT>(issuer, view, j), currency_(currency)
{
}
// Overload with depth parameter for uniformity with MPTIssue version.
// The depth parameter is ignored for IOUs since they don't have vault recursion.
[[nodiscard]] inline bool
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, int /*depth*/)
{
return isFrozen(view, account, issue);
}
/** Constructor for writable context (Issue). */
IOUIssuance(
ApplyView& view,
Issue const& issue,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: AccountRoot<ViewT>(issue.getIssuer(), view, j), currency_(issue.currency)
{
}
[[nodiscard]] bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
/** Constructor for writable context (issuer + currency). */
IOUIssuance(
ApplyView& view,
AccountID const& issuer,
Currency const& currency,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: AccountRoot<ViewT>(issuer, view, j), currency_(currency)
{
}
[[nodiscard]] inline bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Issue const& issue,
int = 0 /*ignored*/)
{
return isDeepFrozen(view, account, issue.currency, issue.account);
}
/** Converting constructor: writable → read-only. */
template <WritableView OtherViewT>
IOUIssuance(IOUIssuance<OtherViewT> const& other)
requires(!kIsWritable)
: AccountRoot<ViewT>(other), currency_(other.getCurrency())
{
}
[[nodiscard]] Currency const&
getCurrency() const
{
return currency_;
}
[[nodiscard]] Issue
getIssue() const
{
return Issue{currency_, this->id()};
}
// --- IOU-specific domain methods ---
/** Returns IOU issuer transfer fee as Rate (delegates to AccountRoot). */
[[nodiscard]] Rate
transferRate() const override
{
return AccountRoot<ViewT>::transferRate();
}
/** Check if the issuer is globally frozen (delegates to AccountRoot). */
[[nodiscard]] bool
isGlobalFrozen() const override
{
return AccountRoot<ViewT>::isGlobalFrozen();
}
/** Check if the issuer requires holder authorization (lsfRequireAuth). */
[[nodiscard]] bool
requiresAuth() const override;
/** Check if the issuer has enabled clawback (lsfAllowTrustLineClawback). */
[[nodiscard]] bool
canClawback() const override;
/** True if the trust line between `account` and the issuer has the
* issuer-side freeze flag set.
*/
[[nodiscard]] bool
isIndividualFrozen(AccountID const& account) const override;
/** True if the issuance is globally frozen or the trust line is frozen.
* `depth` is ignored for IOUs (no vault recursion). */
[[nodiscard]] bool
isFrozen(AccountID const& account, int depth = 0) const override;
/** True if the trust line has the issuer-side deep-freeze flag set. */
[[nodiscard]] bool
isDeepFrozen(AccountID const& account) const;
/** Check if `account` is allowed to hold this IOU.
* `depth` is ignored for IOUs (no vault recursion). */
[[nodiscard]] TER
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
const override;
/** Check if `from` is allowed to send this IOU to `to`. */
[[nodiscard]] TER
canTransfer(AccountID const& from, AccountID const& to) const override;
// --- Credit / balance ---
/** Maximum amount of this IOU that `account` can hold. */
[[nodiscard]] STAmount
creditLimit(AccountID const& account) const;
[[nodiscard]] IOUAmount
creditLimit2(AccountID const& account) const;
/** Amount of this IOU held by `account`. */
[[nodiscard]] STAmount
creditBalance(AccountID const& account) const;
// --- Trust line operations (writable) ---
/** Create a trust line for this IOU's currency. */
[[nodiscard]] TER
trustCreate(
bool const bSrcHigh,
AccountID const& uSrcAccountID,
AccountID const& uDstAccountID,
uint256 const& uIndex,
WAccountRoot& wrappedAcct,
bool const bAuth,
bool const bNoRipple,
bool const bFreeze,
bool bDeepFreeze,
STAmount const& saBalance,
STAmount const& saLimit,
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
beast::Journal j)
requires kIsWritable;
/** Add an empty trust line for `accountID`. */
[[nodiscard]] TER
addEmptyHolding(AccountID const& accountID, XRPAmount priorBalance, beast::Journal journal)
requires kIsWritable;
/** Remove an empty trust line for `accountID`. */
[[nodiscard]] TER
removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
requires kIsWritable;
/** Issue `amount` of this IOU from the issuer to `account`. */
[[nodiscard]] TER
issue(AccountID const& account, STAmount const& amount, beast::Journal j)
requires kIsWritable;
/** Redeem `amount` of this IOU from `account` back to the issuer. */
[[nodiscard]] TER
redeem(AccountID const& account, STAmount const& amount, beast::Journal j)
requires kIsWritable;
};
// CTAD deduction guides — bare IOUIssuance(view, ...) always deduces read-only.
// For writable access, use WIOUIssuance(view, ...) explicitly.
IOUIssuance(ReadView const&, Issue const&) -> IOUIssuance<ReadView>;
IOUIssuance(ReadView const&, Issue const&, beast::Journal) -> IOUIssuance<ReadView>;
IOUIssuance(ReadView const&, AccountID const&, Currency const&) -> IOUIssuance<ReadView>;
IOUIssuance(ReadView const&, AccountID const&, Currency const&, beast::Journal)
-> IOUIssuance<ReadView>;
// Backward-compatible aliases
using RIOUIssuance = IOUIssuance<ReadView>;
using WIOUIssuance = IOUIssuance<ApplyView>;
// Explicit instantiation declarations (definitions in .cpp)
extern template class IOUIssuance<ReadView>;
extern template class IOUIssuance<ApplyView>;
[[nodiscard]] inline TER
checkDeepFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isDeepFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
}
//------------------------------------------------------------------------------
//
// Trust line operations (SLE-level, no issuance context)
// Trust line operations
//
//------------------------------------------------------------------------------
/** Create a trust line
This can set an initial balance.
*/
[[nodiscard]] TER
trustCreate(
ApplyView& view,
bool const bSrcHigh,
AccountID const& uSrcAccountID,
AccountID const& uDstAccountID,
uint256 const& uIndex, // --> ripple state entry
SLE::ref sleAccount, // --> the account being set.
bool const bAuth, // --> authorize account.
bool const bNoRipple, // --> others cannot ripple through
bool const bFreeze, // --> funds cannot leave
bool bDeepFreeze, // --> can neither receive nor send funds
STAmount const& saBalance, // --> balance of account being set.
// Issuer should be noAccount()
STAmount const& saLimit, // --> limit for account being set.
// Issuer should be the account being set.
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
beast::Journal j);
[[nodiscard]] TER
trustDelete(
ApplyView& view,
@@ -242,6 +159,88 @@ trustDelete(
AccountID const& uHighAccountID,
beast::Journal j);
//------------------------------------------------------------------------------
//
// IOU issuance/redemption
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
issueIOU(
ApplyView& view,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
beast::Journal j);
[[nodiscard]] TER
redeemIOU(
ApplyView& view,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
beast::Journal j);
//------------------------------------------------------------------------------
//
// Authorization and transfer checks (IOU-specific)
//
//------------------------------------------------------------------------------
/** Check if the account lacks required authorization.
*
* Return tecNO_AUTH or tecNO_LINE if it does
* and tesSUCCESS otherwise.
*
* If StrongAuth then return tecNO_LINE if the RippleState doesn't exist. Return
* tecNO_AUTH if lsfRequireAuth is set on the issuer's AccountRoot, and the
* RippleState does exist, and the RippleState is not authorized.
*
* If WeakAuth then return tecNO_AUTH if lsfRequireAuth is set, and the
* RippleState exists, and is not authorized. Return tecNO_LINE if
* lsfRequireAuth is set and the RippleState doesn't exist. Consequently, if
* WeakAuth and lsfRequireAuth is *not* set, this function will return
* tesSUCCESS even if RippleState does *not* exist.
*
* The default "Legacy" auth type is equivalent to WeakAuth.
*/
[[nodiscard]] TER
requireAuth(
ReadView const& view,
Issue const& issue,
AccountID const& account,
AuthType authType = AuthType::Legacy);
/** Check if the destination account is allowed
* to receive IOU. Return terNO_RIPPLE if rippling is
* disabled on both sides and tesSUCCESS otherwise.
*/
[[nodiscard]] TER
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to);
//------------------------------------------------------------------------------
//
// Empty holding operations (IOU-specific)
//
//------------------------------------------------------------------------------
/// Any transactors that call addEmptyHolding() in doApply must call
/// canAddHolding() in preflight with the same View and Asset
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,
beast::Journal journal);
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
Issue const& issue,
beast::Journal journal);
/** Delete trustline to AMM. The passed `sle` must be obtained from a prior
* call to view.peek(). Fail if neither side of the trustline is AMM or
* if ammAccountID is seated and is not one of the trustline's side.
@@ -253,4 +252,14 @@ deleteAMMTrustLine(
std::optional<AccountID> const& ammAccountID,
beast::Journal j);
/** Delete AMMs MPToken. The passed `sle` must be obtained from a prior
* call to view.peek().
*/
[[nodiscard]] TER
deleteAMMMPToken(
ApplyView& view,
std::shared_ptr<SLE> sleMPT,
AccountID const& ammAccountID,
beast::Journal j);
} // namespace xrpl

View File

@@ -1,240 +0,0 @@
#pragma once
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <concepts>
#include <memory>
#include <stdexcept>
#include <type_traits>
namespace xrpl {
// Concept to distinguish read-only vs writable view types
template <typename V>
concept WritableView = std::derived_from<V, ApplyView>;
/**
* View-parameterized base class for all ledger entry wrappers.
*
* SLEBase<ReadView> — read-only: holds shared_ptr<SLE const> + ReadView const&
* SLEBase<ApplyView> — writable: holds shared_ptr<SLE> + ApplyView& + Keylet,
* plus insert/update/erase operations
*
* Write-only members are gated by `requires` clauses, providing compile-time
* guarantees that read-only wrappers cannot mutate state.
*
* Derived classes should provide domain-specific accessors that hide
* implementation details of the underlying ledger entry format.
*/
template <typename ViewT>
class SLEBase
{
public:
static constexpr bool kIsWritable = WritableView<ViewT>;
// SLE pointer type: mutable for writable views, const for read-only
using sle_ptr_type = std::conditional_t<kIsWritable, std::shared_ptr<SLE>, SLE::const_pointer>;
// View reference type: ApplyView& for writable, ReadView const& for read-only
using view_ref_type = std::conditional_t<kIsWritable, ApplyView&, ReadView const&>;
virtual ~SLEBase() = default;
SLEBase(SLEBase const&) = default;
SLEBase(SLEBase&&) = default;
SLEBase&
operator=(SLEBase const&) = delete;
SLEBase&
operator=(SLEBase&&) = delete;
SLEBase() = delete;
// --- Common interface (always available) ---
/** Returns true if the ledger entry exists */
[[nodiscard]] bool
exists() const
{
return sle_ != nullptr;
}
/** Explicit conversion to bool for convenient existence checking */
explicit
operator bool() const
{
return exists();
}
/** Returns the underlying SLE for read access */
[[nodiscard]] SLE::const_pointer
sle() const
{
return sle_;
}
/** Returns the read view (always available; ApplyView inherits ReadView) */
[[nodiscard]] ReadView const&
readView() const
{
return view_;
}
/** Const dereference operators (always available) */
STLedgerEntry const*
operator->() const
{
XRPL_ASSERT(exists(), "xrpl::SLEBase::operator-> : exists");
return sle_.get();
}
STLedgerEntry const&
operator*() const
{
XRPL_ASSERT(exists(), "xrpl::SLEBase::operator* : exists");
return *sle_;
}
// --- Writable interface (compile-time gated) ---
/** Returns a mutable SLE for write operations */
[[nodiscard]] sle_ptr_type const&
mutableSle() const
requires kIsWritable
{
return sle_;
}
/** Returns true if this wrapper supports write operations */
[[nodiscard]] bool
canModify() const
requires kIsWritable
{
return sle_ != nullptr;
}
/** Returns the apply view for write operations */
[[nodiscard]] ApplyView&
applyView() const
requires kIsWritable
{
return view_;
}
/** Mutable dereference operators */
STLedgerEntry*
operator->()
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::operator-> : can modify");
return sle_.get();
}
STLedgerEntry&
operator*()
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::operator* : can modify");
return *sle_;
}
void
insert()
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::insert : can modify");
view_.insert(sle_);
}
void
erase()
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::erase : can modify");
view_.erase(sle_);
}
void
update()
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::update : can modify");
view_.update(sle_);
}
void
newSLE()
requires kIsWritable
{
XRPL_ASSERT(!canModify(), "xrpl::SLEBase::newSLE : no existing SLE");
sle_ = std::make_shared<SLE>(key_);
}
[[nodiscard]] beast::Journal
journal() const
{
return j_;
}
protected:
/** Constructor for read-only context */
explicit SLEBase(
SLE::const_pointer sle,
ReadView const& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires(!kIsWritable)
: view_(view), sle_(std::move(sle)), j_(j)
{
}
/** Converting constructor: writable → read-only.
* Enables implicit conversion from SLEBase<ApplyView> to
* SLEBase<ReadView>, so functions taking ReadOnlySLE const& can
* accept WritableSLE.
*/
template <WritableView OtherViewT>
SLEBase(SLEBase<OtherViewT> const& other)
requires(!kIsWritable)
: view_(other.readView()), sle_(other.sle()), j_(other.journal())
{
}
/** Constructor for writable context (from existing SLE) */
explicit SLEBase(
std::shared_ptr<SLE> sle,
ApplyView& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: view_(view)
, key_(sle ? Keylet(sle->getType(), sle->key()) : Keylet(ltANY, uint256{}))
, sle_(std::move(sle))
, j_(j)
{
}
/** Constructor for writable context (peek from view by keylet) */
explicit SLEBase(
Keylet const& key,
ApplyView& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: view_(view), key_(key), sle_(view_.peek(key)), j_(j)
{
}
view_ref_type view_;
// Keylet is only meaningful for writable views, but we conditionally
// include it to avoid wasting space in read-only wrappers.
struct Empty
{
};
[[no_unique_address]]
std::conditional_t<kIsWritable, Keylet, Empty> key_{};
sle_ptr_type sle_;
beast::Journal j_;
};
} // namespace xrpl

View File

@@ -45,50 +45,6 @@ enum class AllowMPTOverflow : bool { No = false, Yes };
*/
enum class AuthType { StrongAuth, WeakAuth, Legacy };
//------------------------------------------------------------------------------
//
// TokenBase<ViewT>: abstract interface shared by MPTokenIssuance and IOUIssuance.
//
// This is a pure-interface base (no data members), used as a second base class
// alongside SLEBase<V> (for MPT) or AccountRoot<V> (for IOU) so callers can hold
// a polymorphic reference and dispatch without knowing the token kind. Methods
// whose semantics differ slightly between IOU and MPT are unified here with the
// MPT signature (e.g. `int depth`); IOU implementations ignore depth.
//
//------------------------------------------------------------------------------
template <typename ViewT>
class TokenBase
{
public:
virtual ~TokenBase() = default;
[[nodiscard]] virtual bool
isGlobalFrozen() const = 0;
[[nodiscard]] virtual bool
isIndividualFrozen(AccountID const& account) const = 0;
[[nodiscard]] virtual bool
isFrozen(AccountID const& account, int depth = 0) const = 0;
[[nodiscard]] virtual Rate
transferRate() const = 0;
[[nodiscard]] virtual TER
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
const = 0;
[[nodiscard]] virtual TER
canTransfer(AccountID const& from, AccountID const& to) const = 0;
[[nodiscard]] virtual bool
canClawback() const = 0;
[[nodiscard]] virtual bool
requiresAuth() const = 0;
};
//------------------------------------------------------------------------------
//
// Freeze checking (Asset-based dispatchers)
@@ -160,10 +116,10 @@ checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& ass
// Returns the amount an account can spend.
//
// If SpendableHandling::SimpleBalance is specified, this is the amount the account can spend
// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
// without going into debt.
//
// If SpendableHandling::FullBalance is specified, this is the amount the account can spend
// If shFULL_BALANCE is specified, this is the amount the account can spend
// total. Specifically:
// * The account can go into debt if using a trust line, and the other side has
// a non-zero limit.
@@ -289,7 +245,7 @@ canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, Acc
// Direct send w/o fees:
// - Redeeming IOUs and/or sending sender's own IOUs.
// - Create trust line of needed.
// bCheckIssuer : normally require issuer to be involved.
// --> bCheckIssuer : normally require issuer to be involved.
// [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles.
/** Calls static directSendNoFeeIOU if saAmount represents Issue.

View File

@@ -287,7 +287,7 @@ credential(uint256 const& key) noexcept
}
Keylet
mptIssuance(std::uint32_t const seq, AccountID const& issuer) noexcept;
mptIssuance(std::uint32_t seq, AccountID const& issuer) noexcept;
Keylet
mptIssuance(MPTID const& issuanceID) noexcept;

View File

@@ -251,6 +251,12 @@ constexpr std::uint8_t kVaultMaximumIouScale = 18;
* another vault; counted from 0 */
constexpr std::uint8_t kMaxAssetCheckDepth = 5;
/** Maximum length of a Data field in Escrow object that can be updated by WASM code. */
constexpr std::size_t kMaxWasmDataLength = 1 * 1024; // 1KB
/** Maximum amount of data transfer across hostfunction<->wasm border. */
constexpr std::size_t kWasmTransferLimit = 1 << 20; // 1MB
/** A ledger index. */
using LedgerIndex = std::uint32_t;

View File

@@ -127,7 +127,9 @@ enum TEMcodes : TERUnderlyingType {
temARRAY_TOO_LARGE,
temBAD_TRANSFER_FEE,
temINVALID_INNER_BATCH,
temBAD_MPT,
temBAD_WASM,
};
//------------------------------------------------------------------------------
@@ -358,6 +360,7 @@ enum TECcodes : TERUnderlyingType {
tecLIMIT_EXCEEDED = 195,
tecPSEUDO_ACCOUNT = 196,
tecPRECISION_LOSS = 197,
tecOUT_OF_GAS = 200,
};
//------------------------------------------------------------------------------

View File

@@ -112,7 +112,7 @@ LEDGER_ENTRY(ltSIGNER_LIST, 0x0053, SignerList, signer_list, ({
/** A ledger object which describes a ticket.
\sa keylet::kTICKET
\sa keylet::kTicket
*/
LEDGER_ENTRY(ltTICKET, 0x0054, Ticket, ticket, ({
{sfAccount, SoeRequired},

View File

@@ -2,7 +2,6 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/WrappedSink.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
@@ -117,7 +116,6 @@ protected:
beast::Journal const j_;
AccountID const accountID_;
WAccountRoot account_;
XRPAmount preFeeBalance_{}; // Balance before fees.
public:

View File

@@ -21,7 +21,7 @@ class TOffer
private:
SLE::pointer entry_;
Quality quality_{};
AccountID account_;
AccountID accountID_;
Asset assetIn_;
Asset assetOut_;
@@ -53,7 +53,7 @@ public:
[[nodiscard]] AccountID const&
owner() const
{
return account_;
return accountID_;
}
/** Returns the in and out amounts.
@@ -122,7 +122,7 @@ public:
isFunded() const
{
// Offer owner is issuer; they have unlimited funds if IOU
return account_ == assetOut_.getIssuer() && assetOut_.holds<Issue>();
return accountID_ == assetOut_.getIssuer() && assetOut_.holds<Issue>();
}
static std::pair<std::uint32_t, std::uint32_t>
@@ -159,7 +159,7 @@ public:
template <StepAmount TIn, StepAmount TOut>
TOffer<TIn, TOut>::TOffer(SLE::pointer entry, Quality quality)
: entry_(std::move(entry)), quality_(quality), account_(entry_->getAccountID(sfAccount))
: entry_(std::move(entry)), quality_(quality), accountID_(entry_->getAccountID(sfAccount))
{
auto const tp = entry_->getFieldAmount(sfTakerPays);
auto const tg = entry_->getFieldAmount(sfTakerGets);

View File

@@ -4,7 +4,6 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/UintTypes.h>
@@ -20,10 +19,9 @@ checkFreeze(
XRPL_ASSERT(src != dst, "xrpl::checkFreeze : unequal input accounts");
// check freeze
AccountRoot const acctDst(dst, view);
if (acctDst)
if (auto sle = view.read(keylet::account(dst)))
{
if (acctDst->isFlag(lsfGlobalFreeze))
if (sle->isFlag(lsfGlobalFreeze))
{
return terNO_LINE;
}
@@ -45,9 +43,10 @@ checkFreeze(
if (view.rules().enabled(fixFrozenLPTokenTransfer))
{
if (AccountRoot const acctDst2(dst, view); acctDst2 && acctDst2->isFieldPresent(sfAMMID))
if (auto const sleDst = view.read(keylet::account(dst));
sleDst && sleDst->isFieldPresent(sfAMMID))
{
auto const sleAmm = view.read(keylet::amm(acctDst2->at(sfAMMID)));
auto const sleAmm = view.read(keylet::amm((*sleDst)[sfAMMID]));
if (!sleAmm)
return tecINTERNAL; // LCOV_EXCL_LINE

View File

@@ -22,7 +22,7 @@ public:
private:
// Tx account owner is required to get the AMM trading fee in BookStep
AccountID account_;
AccountID accountID_;
// true if payment has multiple paths
bool multiPath_{false};
// Is true if AMM offer is consumed during a payment engine iteration.
@@ -31,7 +31,8 @@ private:
std::uint16_t ammIters_{0};
public:
AMMContext(AccountID const& account, bool multiPath) : account_(account), multiPath_(multiPath)
AMMContext(AccountID const& account, bool multiPath)
: accountID_(account), multiPath_(multiPath)
{
}
~AMMContext() = default;
@@ -80,7 +81,7 @@ public:
[[nodiscard]] AccountID
account() const
{
return account_;
return accountID_;
}
/** Strand execution may fail. Reset the flag at the start

View File

@@ -0,0 +1,37 @@
#pragma once
#include <xrpl/basics/Expected.h>
#include <xrpl/tx/wasm/CodecEnvelope.h>
namespace xrpl::wasm {
// ─────────────────────────────────────────────────────────────────────────
// Public wasm codec API.
//
// This header is what host-function code includes. It intentionally contains
// NO codec machinery, NO registered-codec list, and NO concrete type
// registrations — only the two entry points. Their definitions are provided by
// explicit instantiation in src/libxrpl/tx/wasm/codecs/CodecRegistry.cpp, so
// including this header does not instantiate the (consteval) dispatch tables.
//
// A decode<T>/encode<T> for an unregistered T is a link error by design.
//
// To add a codec, see include/xrpl/tx/wasm/detail/codec_types.macro.
// ─────────────────────────────────────────────────────────────────────────
// Decode a value of the statically-known type T from a self-describing
// envelope. The envelope carries its own (type_code, version), so no revision
// context is needed here.
template <typename T>
[[nodiscard]] Expected<T, CodecError>
decode(CodecEnvelope const& env);
// Encode `value` toward a contract that declared codec revision
// `declaredRevision`. The wire version emitted is chosen by the revision policy
// (wireVersionFor); if the value cannot be represented in that version the
// result is NotRepresentable (the graceful-fail path).
template <typename T>
[[nodiscard]] Expected<CodecEnvelope, CodecError>
encode(CodecVersion declaredRevision, T const& value);
} // namespace xrpl::wasm

View File

@@ -0,0 +1,35 @@
#pragma once
#include <cstdint>
namespace xrpl::wasm {
using CodecTypeCode = std::uint16_t;
using CodecVersion = std::uint8_t;
using CodecLength = std::uint32_t;
enum class CodecError : std::uint8_t {
Ok = 0,
UnknownType = 1, // no type code registered for the type
UnsupportedVersion = 2, // The envelope's version has no registered codec
TypeMismatch = 3, // When decoding, the codec called does not match the type of the envelope
Truncated = 4, // payload size != fixed payload size
NotRepresentable = 5, // when encoding, the value doesn't fit within the requested version
TypeNotInRevision = 6, // There is no codec for the type in the requested version
UnknownCodecVersion = 7, // The codec version is not registered
};
// Defines the header of the binary payload for each parameter based through the host functions
struct CodecEnvelopeHeader {
CodecTypeCode type_code;
CodecVersion version;
CodecLength payload_size; // I would prefer a variable sized integer, will need to revisit this part.
};
// Defines the binary payload for each parameter based through the host functions
struct CodecEnvelope {
CodecEnvelopeHeader header;
std::uint8_t const* payload; // Unclear if this can be a pointer into the wasm payload or a vector holding a copy.
};
} // namespace xrpl::wasm

View File

@@ -0,0 +1,516 @@
#pragma once
#include <xrpl/basics/Expected.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <stdexcept>
#include <string>
namespace xrpl {
namespace wasm_float {
std::string
floatToString(Slice const& data);
Expected<Bytes, HostFunctionError>
floatFromIntImpl(int64_t x, int32_t mode);
Expected<Bytes, HostFunctionError>
floatFromUintImpl(uint64_t x, int32_t mode);
Expected<Bytes, HostFunctionError>
floatFromSTAmountImpl(STAmount const& x, int32_t mode);
Expected<Bytes, HostFunctionError>
floatFromSTNumberImpl(STNumber const& x, int32_t mode);
Expected<int64_t, HostFunctionError>
floatToIntImpl(Slice const& x, int32_t mode);
Expected<FloatPair, HostFunctionError>
floatToMantExpImpl(Slice const& x);
Expected<Bytes, HostFunctionError>
floatFromMantExpImpl(int64_t mantissa, int32_t exponent, int32_t mode);
Expected<int32_t, HostFunctionError>
floatCompareImpl(Slice const& x, Slice const& y);
Expected<Bytes, HostFunctionError>
floatAddImpl(Slice const& x, Slice const& y, int32_t mode);
Expected<Bytes, HostFunctionError>
floatSubtractImpl(Slice const& x, Slice const& y, int32_t mode);
Expected<Bytes, HostFunctionError>
floatMultiplyImpl(Slice const& x, Slice const& y, int32_t mode);
Expected<Bytes, HostFunctionError>
floatDivideImpl(Slice const& x, Slice const& y, int32_t mode);
Expected<Bytes, HostFunctionError>
floatRootImpl(Slice const& x, int32_t n, int32_t mode);
Expected<Bytes, HostFunctionError>
floatPowerImpl(Slice const& x, int32_t n, int32_t mode);
} // namespace wasm_float
// Intended to work only through wasm runtime. Don't call them directly, except with unit tests
class HostFunctions
{
protected:
RTOptRef rt_;
beast::Journal j_;
public:
HostFunctions(beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) : j_(j)
{
}
void
setRT(WasmRuntimeWrapper& rt)
{
rt_ = rt;
}
void
resetRT()
{
rt_ = std::nullopt;
}
[[nodiscard]] WasmRuntimeWrapper&
getRT() const
{
if (!rt_)
Throw<std::logic_error>("Wasm runtime not set");
return rt_->get();
}
[[nodiscard]] beast::Journal
getJournal() const
{
return j_;
}
// LCOV_EXCL_START
[[nodiscard]] virtual bool
checkSelf() const
{
return true;
}
virtual Expected<std::uint32_t, HostFunctionError>
getLedgerSqn() const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<std::uint32_t, HostFunctionError>
getParentLedgerTime() const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Hash, HostFunctionError>
getParentLedgerHash() const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<uint32_t, HostFunctionError>
getBaseFee() const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
isAmendmentEnabled(uint256 const& amendmentId) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
isAmendmentEnabled(std::string_view const& amendmentName) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
cacheLedgerObj(uint256 const& objId, int32_t cacheIdx)
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getTxField(SField const& fname) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getCurrentLedgerObjField(SField const& fname) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getLedgerObjField(int32_t cacheIdx, SField const& fname) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getTxNestedField(FieldLocator const& locator) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getCurrentLedgerObjNestedField(FieldLocator const& locator) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getTxArrayLen(SField const& fname) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getCurrentLedgerObjArrayLen(SField const& fname) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getTxNestedArrayLen(FieldLocator const& locator) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
updateData(Slice const& data)
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Hash, HostFunctionError>
computeSha512HalfHash(Slice const& data) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
accountKeylet(AccountID const& account) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
ammKeylet(Asset const& issue1, Asset const& issue2) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
checkKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType)
const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
didKeylet(AccountID const& account) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
delegateKeylet(AccountID const& account, AccountID const& authorize) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
escrowKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
lineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
mptokenKeylet(MPTID const& mptid, AccountID const& holder) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
nftOfferKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
offerKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
oracleKeylet(AccountID const& account, std::uint32_t docId) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
paychanKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
signersKeylet(AccountID const& account) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
ticketKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
vaultKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getNFT(AccountID const& account, uint256 const& nftId) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getNFTIssuer(uint256 const& nftId) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<std::uint32_t, HostFunctionError>
getNFTTaxon(uint256 const& nftId) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getNFTFlags(uint256 const& nftId) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getNFTTransferFee(uint256 const& nftId) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<std::uint32_t, HostFunctionError>
getNFTSerial(uint256 const& nftId) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
trace(std::string_view const& msg, Slice const& data, bool asHex) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
traceNum(std::string_view const& msg, int64_t data) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
traceAccount(std::string_view const& msg, AccountID const& account) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
traceFloat(std::string_view const& msg, Slice const& data) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
traceAmount(std::string_view const& msg, STAmount const& amount) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatFromInt(int64_t x, int32_t mode) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatFromUint(uint64_t x, int32_t mode) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatFromSTAmount(STAmount const& x, int32_t mode) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatFromSTNumber(STNumber const& x, int32_t mode) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int64_t, HostFunctionError>
floatToInt(Slice const& x, int32_t mode) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<FloatPair, HostFunctionError>
floatToMantExp(Slice const& x) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
floatCompare(Slice const& x, Slice const& y) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatAdd(Slice const& x, Slice const& y, int32_t mode) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatSubtract(Slice const& x, Slice const& y, int32_t mode) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatMultiply(Slice const& x, Slice const& y, int32_t mode) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatDivide(Slice const& x, Slice const& y, int32_t mode) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatRoot(Slice const& x, int32_t n, int32_t mode) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatPower(Slice const& x, int32_t n, int32_t mode) const
{
return Unexpected(HostFunctionError::Unimplemented);
}
virtual ~HostFunctions() = default;
// LCOV_EXCL_STOP
};
using HFRef = std::reference_wrapper<HostFunctions>;
} // namespace xrpl

View File

@@ -0,0 +1,283 @@
#pragma once
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/wasm/HostFunc.h>
namespace xrpl {
// Intended to work only through wasm runtime. Don't call them directly, except with unit tests
class WasmHostFunctionsImpl : public HostFunctions
{
ApplyContext& ctx_;
Keylet leKey_;
mutable std::optional<std::shared_ptr<SLE const>> currentLedgerObj_;
static int constexpr maxCache = 256;
std::array<std::shared_ptr<SLE const>, maxCache> cache_;
std::optional<Bytes> data_;
public:
Expected<std::shared_ptr<SLE const>, HostFunctionError>
getCurrentLedgerObj() const
{
if (!currentLedgerObj_)
currentLedgerObj_ = ctx_.view().read(leKey_);
if (*currentLedgerObj_)
return *currentLedgerObj_;
return Unexpected(HostFunctionError::LedgerObjNotFound);
}
Expected<int32_t, HostFunctionError>
normalizeCacheIndex(int32_t cacheIdx) const
{
--cacheIdx;
if (cacheIdx < 0 || cacheIdx >= maxCache)
return Unexpected(HostFunctionError::SlotOutRange);
if (!cache_[cacheIdx])
return Unexpected(HostFunctionError::EmptySlot);
return cacheIdx;
}
template <typename F>
void
log(std::string_view const& msg, F&& dataFn) const
{
#ifdef DEBUG_OUTPUT
auto& j = std::cerr;
#else
if (!getJournal().active(beast::Severity::Trace))
return;
auto j = getJournal().trace();
#endif
j << "WasmTrace[" << toShortString(leKey_.key) << "]: " << msg << " " << dataFn();
#ifdef DEBUG_OUTPUT
j << std::endl;
#endif
}
public:
WasmHostFunctionsImpl(ApplyContext& ct, Keylet const& leKey)
: HostFunctions(ct.journal), ctx_(ct), leKey_(leKey)
{
}
bool
checkSelf() const override
{
return !currentLedgerObj_ && !data_ &&
std::ranges::none_of(cache_, [](auto const& p) { return !!p; });
}
std::optional<Bytes> const&
getData() const
{
return data_;
}
Expected<std::uint32_t, HostFunctionError>
getLedgerSqn() const override;
Expected<std::uint32_t, HostFunctionError>
getParentLedgerTime() const override;
Expected<Hash, HostFunctionError>
getParentLedgerHash() const override;
Expected<std::uint32_t, HostFunctionError>
getBaseFee() const override;
Expected<int32_t, HostFunctionError>
isAmendmentEnabled(uint256 const& amendmentId) const override;
Expected<int32_t, HostFunctionError>
isAmendmentEnabled(std::string_view const& amendmentName) const override;
Expected<int32_t, HostFunctionError>
cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) override;
Expected<Bytes, HostFunctionError>
getTxField(SField const& fname) const override;
Expected<Bytes, HostFunctionError>
getCurrentLedgerObjField(SField const& fname) const override;
Expected<Bytes, HostFunctionError>
getLedgerObjField(int32_t cacheIdx, SField const& fname) const override;
Expected<Bytes, HostFunctionError>
getTxNestedField(FieldLocator const& locator) const override;
Expected<Bytes, HostFunctionError>
getCurrentLedgerObjNestedField(FieldLocator const& locator) const override;
Expected<Bytes, HostFunctionError>
getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const override;
Expected<int32_t, HostFunctionError>
getTxArrayLen(SField const& fname) const override;
Expected<int32_t, HostFunctionError>
getCurrentLedgerObjArrayLen(SField const& fname) const override;
Expected<int32_t, HostFunctionError>
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const override;
Expected<int32_t, HostFunctionError>
getTxNestedArrayLen(FieldLocator const& locator) const override;
Expected<int32_t, HostFunctionError>
getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const override;
Expected<int32_t, HostFunctionError>
getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) const override;
Expected<int32_t, HostFunctionError>
updateData(Slice const& data) override;
Expected<int32_t, HostFunctionError>
checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey)
const override;
Expected<Hash, HostFunctionError>
computeSha512HalfHash(Slice const& data) const override;
Expected<Bytes, HostFunctionError>
accountKeylet(AccountID const& account) const override;
Expected<Bytes, HostFunctionError>
ammKeylet(Asset const& issue1, Asset const& issue2) const override;
Expected<Bytes, HostFunctionError>
checkKeylet(AccountID const& account, std::uint32_t seq) const override;
Expected<Bytes, HostFunctionError>
credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType)
const override;
Expected<Bytes, HostFunctionError>
didKeylet(AccountID const& account) const override;
Expected<Bytes, HostFunctionError>
delegateKeylet(AccountID const& account, AccountID const& authorize) const override;
Expected<Bytes, HostFunctionError>
depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const override;
Expected<Bytes, HostFunctionError>
escrowKeylet(AccountID const& account, std::uint32_t seq) const override;
Expected<Bytes, HostFunctionError>
lineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency)
const override;
Expected<Bytes, HostFunctionError>
mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const override;
Expected<Bytes, HostFunctionError>
mptokenKeylet(MPTID const& mptid, AccountID const& holder) const override;
Expected<Bytes, HostFunctionError>
nftOfferKeylet(AccountID const& account, std::uint32_t seq) const override;
Expected<Bytes, HostFunctionError>
offerKeylet(AccountID const& account, std::uint32_t seq) const override;
Expected<Bytes, HostFunctionError>
oracleKeylet(AccountID const& account, std::uint32_t docId) const override;
Expected<Bytes, HostFunctionError>
paychanKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq)
const override;
Expected<Bytes, HostFunctionError>
permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const override;
Expected<Bytes, HostFunctionError>
signersKeylet(AccountID const& account) const override;
Expected<Bytes, HostFunctionError>
ticketKeylet(AccountID const& account, std::uint32_t seq) const override;
Expected<Bytes, HostFunctionError>
vaultKeylet(AccountID const& account, std::uint32_t seq) const override;
Expected<Bytes, HostFunctionError>
getNFT(AccountID const& account, uint256 const& nftId) const override;
Expected<Bytes, HostFunctionError>
getNFTIssuer(uint256 const& nftId) const override;
Expected<std::uint32_t, HostFunctionError>
getNFTTaxon(uint256 const& nftId) const override;
Expected<int32_t, HostFunctionError>
getNFTFlags(uint256 const& nftId) const override;
Expected<int32_t, HostFunctionError>
getNFTTransferFee(uint256 const& nftId) const override;
Expected<std::uint32_t, HostFunctionError>
getNFTSerial(uint256 const& nftId) const override;
Expected<int32_t, HostFunctionError>
trace(std::string_view const& msg, Slice const& data, bool asHex) const override;
Expected<int32_t, HostFunctionError>
traceNum(std::string_view const& msg, int64_t data) const override;
Expected<int32_t, HostFunctionError>
traceAccount(std::string_view const& msg, AccountID const& account) const override;
Expected<int32_t, HostFunctionError>
traceFloat(std::string_view const& msg, Slice const& data) const override;
Expected<int32_t, HostFunctionError>
traceAmount(std::string_view const& msg, STAmount const& amount) const override;
Expected<Bytes, HostFunctionError>
floatFromInt(int64_t x, int32_t mode) const override;
Expected<Bytes, HostFunctionError>
floatFromUint(uint64_t x, int32_t mode) const override;
Expected<Bytes, HostFunctionError>
floatFromSTAmount(STAmount const& x, int32_t mode) const override;
Expected<Bytes, HostFunctionError>
floatFromSTNumber(STNumber const& x, int32_t mode) const override;
Expected<int64_t, HostFunctionError>
floatToInt(Slice const& x, int32_t mode) const override;
Expected<FloatPair, HostFunctionError>
floatToMantExp(Slice const& x) const override;
Expected<Bytes, HostFunctionError>
floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const override;
Expected<int32_t, HostFunctionError>
floatCompare(Slice const& x, Slice const& y) const override;
Expected<Bytes, HostFunctionError>
floatAdd(Slice const& x, Slice const& y, int32_t mode) const override;
Expected<Bytes, HostFunctionError>
floatSubtract(Slice const& x, Slice const& y, int32_t mode) const override;
Expected<Bytes, HostFunctionError>
floatMultiply(Slice const& x, Slice const& y, int32_t mode) const override;
Expected<Bytes, HostFunctionError>
floatDivide(Slice const& x, Slice const& y, int32_t mode) const override;
Expected<Bytes, HostFunctionError>
floatRoot(Slice const& x, int32_t n, int32_t mode) const override;
Expected<Bytes, HostFunctionError>
floatPower(Slice const& x, int32_t n, int32_t mode) const override;
};
} // namespace xrpl

View File

@@ -0,0 +1,254 @@
#pragma once
#include <xrpl/tx/wasm/WasmImportsHelper.h>
#include <wasm.h>
#include <cstdint>
namespace xrpl {
#define WASM_CB_PARAMS_LIST void *env, wasm_val_vec_t const *params, wasm_val_vec_t *results
#define WASM_SECONDARY_CB_PARAMS_LIST \
HostFunctions &hf, wasm_val_vec_t const *params, wasm_val_vec_t *results
wasm_trap_t* HostFuncMain_wrap(WASM_CB_PARAMS_LIST);
using getLedgerSqn_proto = int32_t(uint8_t*, int32_t);
wasm_trap_t* getLedgerSqn_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getParentLedgerTime_proto = int32_t(uint8_t*, int32_t);
wasm_trap_t* getParentLedgerTime_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getParentLedgerHash_proto = int32_t(uint8_t*, int32_t);
wasm_trap_t* getParentLedgerHash_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getBaseFee_proto = int32_t(uint8_t*, int32_t);
wasm_trap_t* getBaseFee_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using isAmendmentEnabled_proto = int32_t(uint8_t const*, int32_t);
wasm_trap_t* isAmendmentEnabled_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using cacheLedgerObj_proto = int32_t(uint8_t const*, int32_t, int32_t);
wasm_trap_t* cacheLedgerObj_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getTxField_proto = int32_t(int32_t, uint8_t*, int32_t);
wasm_trap_t* getTxField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getCurrentLedgerObjField_proto = int32_t(int32_t, uint8_t*, int32_t);
wasm_trap_t* getCurrentLedgerObjField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getLedgerObjField_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t);
wasm_trap_t* getLedgerObjField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getTxNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* getTxNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getCurrentLedgerObjNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* getCurrentLedgerObjNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getLedgerObjNestedField_proto = int32_t(int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* getLedgerObjNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getTxArrayLen_proto = int32_t(int32_t);
wasm_trap_t* getTxArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getCurrentLedgerObjArrayLen_proto = int32_t(int32_t);
wasm_trap_t* getCurrentLedgerObjArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getLedgerObjArrayLen_proto = int32_t(int32_t, int32_t);
wasm_trap_t* getLedgerObjArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getTxNestedArrayLen_proto = int32_t(uint8_t const*, int32_t);
wasm_trap_t* getTxNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getCurrentLedgerObjNestedArrayLen_proto = int32_t(uint8_t const*, int32_t);
wasm_trap_t* getCurrentLedgerObjNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getLedgerObjNestedArrayLen_proto = int32_t(int32_t, uint8_t const*, int32_t);
wasm_trap_t* getLedgerObjNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using updateData_proto = int32_t(uint8_t const*, int32_t);
wasm_trap_t* updateData_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using checkSignature_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t const*, int32_t);
wasm_trap_t* checkSignature_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using computeSha512HalfHash_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* computeSha512HalfHash_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using accountKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* accountKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using ammKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* ammKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using checkKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* checkKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using credentialKeylet_proto = int32_t(
uint8_t const*,
int32_t,
uint8_t const*,
int32_t,
uint8_t const*,
int32_t,
uint8_t*,
int32_t);
wasm_trap_t* credentialKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using delegateKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* delegateKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using depositPreauthKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* depositPreauthKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using didKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* didKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using escrowKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* escrowKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using lineKeylet_proto = int32_t(
uint8_t const*,
int32_t,
uint8_t const*,
int32_t,
uint8_t const*,
int32_t,
uint8_t*,
int32_t);
wasm_trap_t* lineKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using mptIssuanceKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* mptIssuanceKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using mptokenKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* mptokenKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using nftOfferKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* nftOfferKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using offerKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* offerKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using oracleKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* oracleKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using paychanKeylet_proto = int32_t(
uint8_t const*,
int32_t,
uint8_t const*,
int32_t,
uint8_t const*,
int32_t,
uint8_t*,
int32_t);
wasm_trap_t* paychanKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using permissionedDomainKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* permissionedDomainKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using signersKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* signersKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using ticketKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* ticketKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using vaultKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* vaultKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getNFT_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* getNFT_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getNFTIssuer_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* getNFTIssuer_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getNFTTaxon_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* getNFTTaxon_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getNFTFlags_proto = int32_t(uint8_t const*, int32_t);
wasm_trap_t* getNFTFlags_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getNFTTransferFee_proto = int32_t(uint8_t const*, int32_t);
wasm_trap_t* getNFTTransferFee_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using getNFTSerial_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* getNFTSerial_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using trace_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, int32_t);
wasm_trap_t* trace_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using traceNum_proto = int32_t(uint8_t const*, int32_t, int64_t);
wasm_trap_t* traceNum_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using traceAccount_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
wasm_trap_t* traceAccount_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using traceFloat_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
wasm_trap_t* traceFloat_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using traceAmount_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
wasm_trap_t* traceAmount_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatFromInt_proto = int32_t(int64_t, uint8_t*, int32_t, int32_t);
wasm_trap_t* floatFromInt_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatFromUint_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
wasm_trap_t* floatFromUint_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatFromSTAmount_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
wasm_trap_t* floatFromSTAmount_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatFromSTNumber_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
wasm_trap_t* floatFromSTNumber_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatToInt_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
wasm_trap_t* floatToInt_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatToMantExp_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, uint8_t*, int32_t);
wasm_trap_t* floatToMantExp_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatFromMantExp_proto = int32_t(int64_t, int32_t, uint8_t*, int32_t, int32_t);
wasm_trap_t* floatFromMantExp_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatCompare_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
wasm_trap_t* floatCompare_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatAdd_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
wasm_trap_t* floatAdd_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatSubtract_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
wasm_trap_t* floatSubtract_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatMultiply_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
wasm_trap_t* floatMultiply_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatDivide_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
wasm_trap_t* floatDivide_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatRoot_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t);
wasm_trap_t* floatRoot_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using floatPower_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t);
wasm_trap_t* floatPower_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
} // namespace xrpl

View File

@@ -0,0 +1,189 @@
# WASM Module for Programmable Escrows
This module provides WebAssembly (WASM) execution capabilities for programmable
escrows on the XRP Ledger. When an escrow is finished, the WASM code runs to
determine whether the escrow conditions are met, enabling custom programmable
logic for escrow release conditions.
For the full specification, see
[XLS-0102: WASM VM](https://xls.xrpl.org/xls/XLS-0102-wasm-vm.html).
## Architecture
The module follows a layered architecture:
```
┌─────────────────────────────────────────────────────────────┐
│ WasmEngine (WasmVM.h) │
│ runEscrowWasm(), preflightEscrowWasm() │
│ Host function registration │
├─────────────────────────────────────────────────────────────┤
│ WasmiEngine (WasmiVM.h) │
│ Low-level wasmi interpreter integration │
├─────────────────────────────────────────────────────────────┤
│ HostFuncWrapper │ HostFuncImpl │
│ C-style WASM bridges │ C++ implementations │
├─────────────────────────────────────────────────────────────┤
│ HostFunc (Interface) │
│ Abstract base class for host functions │
└─────────────────────────────────────────────────────────────┘
```
### Key Components
- **`WasmVM.h` / `detail/WasmVM.cpp`** - High-level facade providing:
- `WasmEngine` singleton that wraps the underlying WASM interpreter
- `runEscrowWasm()` - Execute WASM code for escrow finish
- `preflightEscrowWasm()` - Validate WASM code during preflight
- `createWasmImport()` - Register all host functions
- **`WasmiVM.h` / `detail/WasmiVM.cpp`** - Low-level integration with the
[wasmi](https://github.com/wasmi-labs/wasmi) WebAssembly interpreter:
- `WasmiEngine` - Manages WASM modules, instances, and execution
- Memory management and gas metering
- Function invocation and result handling
- **`HostFunc.h`** - Abstract `HostFunctions` base class defining the interface
for all callable host functions. Each method returns
`Expected<T, HostFunctionError>`.
- **`HostFuncImpl.h` / `detail/HostFuncImpl*.cpp`** - Concrete
`WasmHostFunctionsImpl` class that implements host functions with access to
`ApplyContext` for ledger state queries. Implementation split across files:
- `HostFuncImpl.cpp` - Core utilities (updateData, checkSignature, etc.)
- `HostFuncImplFloat.cpp` - Float/number arithmetic operations
- `HostFuncImplGetter.cpp` - Field access (transaction, ledger objects)
- `HostFuncImplKeylet.cpp` - Keylet construction functions
- `HostFuncImplLedgerHeader.cpp` - Ledger header info access
- `HostFuncImplNFT.cpp` - NFT-related queries
- `HostFuncImplTrace.cpp` - Debugging/tracing functions
- **`HostFuncWrapper.h` / `detail/HostFuncWrapper.cpp`** - C-style wrapper
functions that bridge WASM calls to C++ `HostFunctions` methods. Each host
function has:
- A `_proto` type alias defining the function signature
- A `_wrap` function that extracts parameters and calls the implementation
- **`ParamsHelper.h`** - Utilities for WASM parameter handling:
- `WASM_IMPORT_FUNC` / `WASM_IMPORT_FUNC2` macros for registration
- `wasmParams()` helper for building parameter vectors
- Type conversion between WASM and C++ types
## Host Functions
Host functions allow WASM code to interact with the XRP Ledger. They are
organized into categories:
- **Ledger Information** - Access ledger sequence, timestamps, hashes, fees
- **Transaction & Ledger Object Access** - Read fields from the transaction
and ledger objects (including the current escrow object)
- **Keylet Construction** - Build keylets to look up various ledger object types
- **Cryptography** - Signature verification and hashing
- **Float Arithmetic** - Mathematical operations for amount calculations
- **NFT Operations** - Query NFT properties
- **Tracing/Debugging** - Log messages for debugging
For the complete list of available host functions, their WASM names, and gas
costs, see the [XLS-0102 specification](https://xls.xrpl.org/xls/XLS-0102-wasm-vm.html)
or `detail/WasmVM.cpp` where they are registered via `WASM_IMPORT_FUNC2` macros.
For method signatures, see `HostFunc.h`.
## Gas Model
Each host function has an associated gas cost. The gas cost is specified when
registering the function in `detail/WasmVM.cpp`:
```cpp
WASM_IMPORT_FUNC2(i, getLedgerSqn, "get_ledger_sqn", hfs, 60);
// ^^ gas cost
```
WASM execution is metered, and if the gas limit is exceeded, execution fails.
## Entry Point
The WASM module must export a function with the name defined by
`ESCROW_FUNCTION_NAME` (currently `"finish"`). This function:
- Takes no parameters (or parameters passed via host function calls)
- Returns an `int32_t`:
- `1` (or positive): Escrow conditions are met, allow finish
- `0` (or negative): Escrow conditions are not met, reject finish
## Adding a New Host Function
To add a new host function, follow these steps:
### 1. Add to HostFunc.h (Base Class)
Add a virtual method declaration with a default implementation that returns an
error:
```cpp
virtual Expected<ReturnType, HostFunctionError>
myNewFunction(ParamType1 param1, ParamType2 param2)
{
return Unexpected(HostFunctionError::INTERNAL);
}
```
### 2. Add to HostFuncImpl.h (Declaration)
Add the method override declaration in `WasmHostFunctionsImpl`:
```cpp
Expected<ReturnType, HostFunctionError>
myNewFunction(ParamType1 param1, ParamType2 param2) override;
```
### 3. Implement in detail/HostFuncImpl\*.cpp
Add the implementation in the appropriate file:
```cpp
Expected<ReturnType, HostFunctionError>
WasmHostFunctionsImpl::myNewFunction(ParamType1 param1, ParamType2 param2)
{
// Implementation using ctx (ApplyContext) for ledger access
return result;
}
```
### 4. Add Wrapper to HostFuncWrapper.h
Add the prototype and wrapper declaration:
```cpp
using myNewFunction_proto = int32_t(uint8_t const*, int32_t, ...);
wasm_trap_t*
myNewFunction_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
```
### 5. Implement Wrapper in detail/HostFuncWrapper.cpp
Implement the C-style wrapper that bridges WASM to C++:
```cpp
wasm_trap_t*
myNewFunction_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
{
// Extract parameters from params
// Call hfs->myNewFunction(...)
// Set results and return
}
```
### 6. Register in WasmVM.cpp
Add the function registration in `setCommonHostFunctions()` or
`createWasmImport()`:
```cpp
WASM_IMPORT_FUNC2(i, myNewFunction, "my_new_function", hfs, 100);
// ^^ WASM name ^^ gas cost
```
> [!IMPORTANT]
> New host functions MUST be amendment-gated in `WasmVM.cpp`.
> Wrap the registration in an amendment check to ensure the function is only
> available after the corresponding amendment is enabled on the network.

View File

@@ -0,0 +1,237 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/protocol/TER.h>
#include <functional>
#include <optional>
#include <stdexcept>
#include <string_view>
#include <vector>
namespace xrpl {
using Bytes = std::vector<std::uint8_t>;
using Hash = xrpl::uint256;
using FloatPair = std::pair<int64_t, int32_t>;
// Error signals that cross the wasm boundary as trap messages (the C API has no
// trap code). WasmiEngine::call maps them to TER: hfErrInternal -> tecINTERNAL,
// hfErrOutOfGas / wasmi's OutOfFuel -> tecOUT_OF_GAS, anything else ->
// tecFAILED_PROCESSING.
//
// Matched as substrings, not by equality: the C API returns the Rust Debug form
// of the error, e.g. `Error { kind: Message("HfInternal") }` or
// `Error { kind: TrapCode(OutOfFuel) }`.
std::string_view inline constexpr hfErrInternal = "HfInternal";
std::string_view inline constexpr hfErrOutOfGas = "HfOutOfGas";
std::string_view inline constexpr wasmiTrapOutOfFuel = "OutOfFuel";
enum class HostFunctionError : int32_t {
Unimplemented = -1,
FieldNotFound = -2,
BufferTooSmall = -3,
NoArray = -4,
NotLeafField = -5,
LocatorMalformed = -6,
SlotOutRange = -7,
SlotsFull = -8,
EmptySlot = -9,
LedgerObjNotFound = -10,
OutOfTransferLimit = -11,
DataFieldTooLarge = -12,
PointerOutOfBounds = -13,
NoMemExported = -14,
InvalidParams = -15,
InvalidAccount = -16,
InvalidField = -17,
IndexOutOfBounds = -18,
FloatInputMalformed = -19,
FloatComputationError = -20,
};
enum class WasmTypes { WtI32, WtI64 };
struct Wmem
{
std::uint8_t* p = nullptr;
std::size_t s = 0;
Wmem() = default;
Wmem(void* ptr, std::size_t size) : p(reinterpret_cast<std::uint8_t*>(ptr)), s(size)
{
}
};
template <typename T>
struct WasmResult
{
T result;
int64_t cost;
};
using EscrowResult = WasmResult<int32_t>;
// Engine error when wasm does not run to completion. `cost` is the gas consumed
// when meaningful (tecOUT_OF_GAS / tecFAILED_PROCESSING; caller writes it to tx
// metadata); std::nullopt for tecINTERNAL and malformed input (no gas reported).
struct WasmTER
{
TER ter;
std::optional<int64_t> cost;
};
class FieldLocator
{
int32_t const* ptr_ = nullptr;
uint32_t size_ = 0;
std::vector<int32_t> buf_;
public:
FieldLocator(std::vector<int32_t>&& buf)
: ptr_(&buf[0]), size_(buf.size()), buf_(std::move(buf))
{
}
FieldLocator(int32_t const* ptr, uint32_t const size) : ptr_(ptr), size_(size)
{
}
FieldLocator(FieldLocator const&) = delete;
FieldLocator&
operator=(FieldLocator const&) = delete;
FieldLocator(FieldLocator&&) = default;
FieldLocator&
operator=(FieldLocator&&) = default;
int32_t
operator[](unsigned i) const
{
if (i >= size_)
Throw<std::runtime_error>("index out of bounds");
return ptr_[i];
}
[[nodiscard]] uint32_t
size() const
{
return size_;
}
[[nodiscard]] int32_t const*
data() const
{
return ptr_;
}
[[nodiscard]] bool
empty() const
{
return size_ == 0;
}
};
class WasmRuntimeWrapper
{
public:
virtual ~WasmRuntimeWrapper() = default;
virtual Wmem
getMem() = 0;
virtual std::int64_t
getGas() = 0;
virtual std::int64_t
setGas(std::int64_t gas) = 0;
virtual std::int64_t
getTransferLimit() = 0;
virtual std::int64_t
setTransferLimit(std::int64_t transferLimit) = 0;
};
using RTOptRef = std::optional<std::reference_wrapper<WasmRuntimeWrapper>>;
struct WasmParam
{
// We are not supporting float/double
WasmTypes type = WasmTypes::WtI32;
union
{
std::int32_t i32;
std::int64_t i64 = 0;
} of;
};
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, std::int32_t p, Types&&... args)
{
v.push_back({.type = WasmTypes::WtI32, .of = {.i32 = p}});
wasmParamsHlp(v, std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, std::int64_t p, Types&&... args)
{
v.push_back({.type = WasmTypes::WtI64, .of = {.i64 = p}});
wasmParamsHlp(v, std::forward<Types>(args)...);
}
inline void
wasmParamsHlp(std::vector<WasmParam>& v)
{
return;
}
template <class... Types>
inline std::vector<WasmParam>
wasmParams(Types&&... args)
{
std::vector<WasmParam> v;
v.reserve(sizeof...(args));
wasmParamsHlp(v, std::forward<Types>(args)...);
return v;
}
template <typename T, size_t Size = sizeof(T)>
constexpr T
adjustWasmEndianessHlp(T x)
{
static_assert(std::is_integral_v<T>, "Only integral types");
if constexpr (Size > 1)
{
using U = std::make_unsigned_t<T>;
U u = static_cast<U>(x);
U const low = (u & 0xFF) << ((Size - 1) << 3);
u = adjustWasmEndianessHlp<U, Size - 1>(u >> 8);
return static_cast<T>(low | u);
}
return x;
}
template <typename T, size_t Size = sizeof(T)>
constexpr T
adjustWasmEndianess(T x)
{
// LCOV_EXCL_START
static_assert(std::is_integral_v<T>, "Only integral types");
if constexpr (std::endian::native == std::endian::big)
{
return adjustWasmEndianessHlp(x);
}
return x;
// LCOV_EXCL_STOP
}
constexpr int32_t
hfErrorToInt(HostFunctionError e)
{
return static_cast<int32_t>(e);
}
} // namespace xrpl

View File

@@ -0,0 +1,128 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/tx/wasm/HostFunc.h>
#include <boost/function_types/function_arity.hpp>
#include <boost/function_types/parameter_types.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/mpl/vector.hpp>
#include <wasm.h>
namespace bft = boost::function_types;
namespace xrpl {
using wasmSecondaryCbFuncType =
wasm_trap_t*(HostFunctions&, wasm_val_vec_t const*, wasm_val_vec_t*);
struct WasmImportFunc
{
std::string_view name;
std::optional<WasmTypes> result;
std::vector<WasmTypes> params;
wasmSecondaryCbFuncType* wrap = nullptr;
uint32_t gas = 0;
};
using WasmUserData = std::pair<HFRef, WasmImportFunc>;
// string - import function name
using ImportVec = std::unordered_map<std::string_view, WasmUserData>;
template <int N, int C, typename Mpl>
void
WasmImpArgs(WasmImportFunc& e)
{
if constexpr (N < C)
{
using at = typename boost::mpl::at_c<Mpl, N>::type;
if constexpr (std::is_pointer_v<at>)
{
e.params.push_back(WasmTypes::WtI32);
}
else if constexpr (std::is_same_v<at, std::int32_t>)
{
e.params.push_back(WasmTypes::WtI32);
}
else if constexpr (std::is_same_v<at, std::int64_t>)
{
e.params.push_back(WasmTypes::WtI64);
}
else
{
static_assert(std::is_pointer_v<at>, "Unsupported argument type");
}
return WasmImpArgs<N + 1, C, Mpl>(e);
}
return;
}
template <typename>
inline constexpr bool wasmDependentFalse = false;
template <typename Rt>
void
WasmImpRet(WasmImportFunc& e)
{
if constexpr (std::is_pointer_v<Rt>)
{
e.result = WasmTypes::WtI32;
}
else if constexpr (std::is_same_v<Rt, std::int32_t>)
{
e.result = WasmTypes::WtI32;
}
else if constexpr (std::is_same_v<Rt, std::int64_t>)
{
e.result = WasmTypes::WtI64;
}
else if constexpr (std::is_void_v<Rt>)
{
e.result.reset();
}
else
{
static_assert(wasmDependentFalse<Rt>, "Unsupported return type");
}
}
template <typename F>
void
WasmImpFuncHelper(WasmImportFunc& e)
{
using rt = typename bft::result_type<F>::type;
using pt = typename bft::parameter_types<F>::type;
// typename boost::mpl::at_c<mpl, N>::type
WasmImpRet<rt>(e);
WasmImpArgs<0, bft::function_arity<F>::value, pt>(e);
// WasmImpWrap(e, std::forward<F>(f));
}
// imp_name - string literal, must have static lifetime
template <typename F>
void
WasmImpFunc(
ImportVec& v,
std::string_view impName,
wasmSecondaryCbFuncType* fWrap,
HostFunctions& hf,
uint32_t gas = 0)
{
WasmImportFunc e;
e.name = impName;
e.wrap = fWrap;
e.gas = gas;
WasmImpFuncHelper<F>(e);
v.emplace(impName, std::make_pair(HFRef(hf), std::move(e)));
}
#define WASM_IMPORT_FUNC(v, f, ...) WasmImpFunc<f##_proto>(v, #f, &f##_wrap, ##__VA_ARGS__)
// n - string literal name, must have static lifetime
#define WASM_IMPORT_FUNC2(v, f, n, ...) WasmImpFunc<f##_proto>(v, n, &f##_wrap, ##__VA_ARGS__)
} // namespace xrpl

View File

@@ -0,0 +1,89 @@
#pragma once
#include <xrpl/tx/wasm/HostFunc.h>
#include <xrpl/tx/wasm/WasmImportsHelper.h>
#include <string_view>
namespace xrpl {
std::string_view inline constexpr wEnv = "env";
std::string_view inline constexpr wHostLib = "host_lib";
std::string_view inline constexpr wMem = "memory";
std::string_view inline constexpr wStore = "store";
std::string_view inline constexpr wLoad = "load";
std::string_view inline constexpr wSize = "size";
std::string_view inline constexpr wAlloc = "allocate";
std::string_view inline constexpr wDealloc = "deallocate";
std::string_view inline constexpr wProcExit = "proc_exit";
std::string_view inline constexpr escrowFunctionName = "finish";
uint32_t inline constexpr maxPages = 128; // 8MB = 64KB*128
class WasmiEngine;
class WasmEngine
{
std::unique_ptr<WasmiEngine> const impl_;
WasmEngine();
public:
WasmEngine(WasmEngine const&) = delete;
WasmEngine(WasmEngine&&) = delete;
WasmEngine&
operator=(WasmEngine const&) = delete;
WasmEngine&
operator=(WasmEngine&&) = delete;
static WasmEngine&
instance();
Expected<WasmResult<int32_t>, WasmTER>
run(Bytes const& wasmCode,
HostFunctions& hfs,
int64_t gasLimit,
std::string_view funcName = {},
std::vector<WasmParam> const& params = {},
ImportVec const& imports = {},
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
NotTEC
check(
Bytes const& wasmCode,
HostFunctions& hfs,
std::string_view funcName,
std::vector<WasmParam> const& params = {},
ImportVec const& imports = {},
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
// Host functions helper functionality
void*
newTrap(std::string const& txt = std::string());
[[nodiscard]] beast::Journal
getJournal() const;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ImportVec
createWasmImport(HostFunctions& hfs);
Expected<EscrowResult, WasmTER>
runEscrowWasm(
Bytes const& wasmCode,
HostFunctions& hfs,
int64_t gasLimit,
std::string_view funcName = escrowFunctionName,
std::vector<WasmParam> const& params = {});
NotTEC
preflightEscrowWasm(
Bytes const& wasmCode,
HostFunctions& hfs,
std::string_view funcName = escrowFunctionName,
std::vector<WasmParam> const& params = {});
} // namespace xrpl

View File

@@ -0,0 +1,446 @@
#pragma once
#include <xrpl/protocol/Protocol.h>
#include <xrpl/tx/wasm/WasmVM.h>
#include <wasm.h>
#include <wasmi.h>
#include <optional>
namespace xrpl {
template <class T, void (*Create)(T*, size_t), void (*Destroy)(T*)>
class WasmVec
{
using TD = std::remove_pointer_t<decltype(T::data)>;
T vec_;
public:
WasmVec(size_t s = 0) : vec_ WASM_EMPTY_VEC
{
if (s > 0)
Create(&vec_, s); // zeroes memory
}
~WasmVec()
{
clear();
}
WasmVec(WasmVec const&) = delete;
WasmVec&
operator=(WasmVec const&) = delete;
WasmVec(WasmVec&& other) noexcept : vec_ WASM_EMPTY_VEC
{
*this = std::move(other);
}
WasmVec&
operator=(WasmVec&& other) noexcept
{
if (this != &other)
{
clear();
vec_ = other.vec_;
other.vec_ = WASM_EMPTY_VEC;
}
return *this;
}
void
clear()
{
Destroy(&vec_); // call destructor for every elements too
vec_ = WASM_EMPTY_VEC;
}
T
release()
{
T result = vec_;
vec_ = WASM_EMPTY_VEC;
return result;
}
T*
get()
{
return &vec_;
}
[[nodiscard]] T const*
get() const
{
return &vec_;
}
TD&
operator[](size_t i)
{
if (i >= vec_.size)
Throw<std::runtime_error>("Out of bound");
return vec_.data[i];
}
TD const&
operator[](size_t i) const
{
if (i >= vec_.size)
Throw<std::runtime_error>("Out of bound");
return vec_.data[i];
}
[[nodiscard]] size_t
size() const
{
return vec_.size;
}
[[nodiscard]] bool
empty() const
{
return vec_.size == 0u;
}
};
using WasmValtypeVec =
WasmVec<wasm_valtype_vec_t, &wasm_valtype_vec_new_uninitialized, &wasm_valtype_vec_delete>;
using WasmValVec = WasmVec<wasm_val_vec_t, &wasm_val_vec_new_uninitialized, &wasm_val_vec_delete>;
using WasmExternVec =
WasmVec<wasm_extern_vec_t, &wasm_extern_vec_new_uninitialized, &wasm_extern_vec_delete>;
using WasmExporttypeVec = WasmVec<
wasm_exporttype_vec_t,
&wasm_exporttype_vec_new_uninitialized,
&wasm_exporttype_vec_delete>;
using WasmImporttypeVec = WasmVec<
wasm_importtype_vec_t,
&wasm_importtype_vec_new_uninitialized,
&wasm_importtype_vec_delete>;
struct WasmiResult
{
WasmValVec r;
// Set iff the call trapped. Holds the TER the trap was classified into
// (tecINTERNAL / tecOUT_OF_GAS / tecFAILED_PROCESSING); see
// WasmiEngine::call. std::nullopt means the call returned normally.
std::optional<TER> ter;
WasmiResult(unsigned n = 0) : r(n)
{
}
WasmiResult() = delete;
~WasmiResult() = default;
WasmiResult(WasmiResult&& o) = default;
WasmiResult&
operator=(WasmiResult&& o) = default;
};
using ModulePtr = std::unique_ptr<wasm_module_t, decltype(&wasm_module_delete)>;
using InstancePtr = std::unique_ptr<wasm_instance_t, decltype(&wasm_instance_delete)>;
using EnginePtr = std::unique_ptr<wasm_engine_t, decltype(&wasm_engine_delete)>;
using StorePtr = std::unique_ptr<wasm_store_t, decltype(&wasm_store_delete)>;
using FuncInfo = std::pair<wasm_func_t const*, wasm_functype_t const*>;
class InstanceWrapper
{
wasm_store_t* store_ = nullptr;
WasmExternVec exports_;
mutable int memIdx_ = -1;
InstancePtr instance_;
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
std::int64_t transferLimit_ = kWasmTransferLimit;
private:
static InstancePtr
init(
StorePtr& s,
ModulePtr& m,
WasmExternVec& expt,
WasmExternVec const& imports,
beast::Journal j);
public:
InstanceWrapper() : instance_(nullptr, &wasm_instance_delete) {};
InstanceWrapper(InstanceWrapper const&) = delete;
InstanceWrapper(InstanceWrapper&& o) : instance_(nullptr, &wasm_instance_delete)
{
*this = std::move(o); // LCOV_EXCL_LINE
}
InstanceWrapper(StorePtr& s, ModulePtr& m, WasmExternVec const& imports, beast::Journal j)
: store_(s.get()), instance_(init(s, m, exports_, imports, j)), j_(j)
{
}
InstanceWrapper&
operator=(InstanceWrapper&& o);
InstanceWrapper&
operator=(InstanceWrapper const&) = delete;
operator bool() const
{
return static_cast<bool>(instance_);
}
FuncInfo
getFunc(std::string_view funcName, WasmExporttypeVec const& exportTypes) const;
Wmem
getMem() const;
std::int64_t
getGas() const;
std::int64_t
setGas(std::int64_t) const;
std::int64_t
getTransferLimit() const;
std::int64_t
setTransferLimit(std::int64_t);
};
class ModuleWrapper
{
ModulePtr module_;
InstanceWrapper instanceWrap_;
WasmExporttypeVec exportTypes_;
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
public:
// LCOV_EXCL_START
ModuleWrapper() : module_(nullptr, &wasm_module_delete)
{
}
ModuleWrapper(ModuleWrapper&& o) : module_(nullptr, &wasm_module_delete)
{
*this = std::move(o);
}
// LCOV_EXCL_STOP
ModuleWrapper&
operator=(ModuleWrapper&& o);
ModuleWrapper(
StorePtr& s,
Bytes const& wasmBin,
bool instantiate,
ImportVec const& imports,
beast::Journal j);
~ModuleWrapper() = default;
operator bool() const
{
return instanceWrap_;
}
FuncInfo
getFunc(std::string_view funcName) const
{
return instanceWrap_.getFunc(funcName, exportTypes_);
}
wasm_functype_t*
getFuncType(std::string_view funcName) const;
Wmem
getMem() const
{
return instanceWrap_.getMem();
}
InstanceWrapper&
getInstance(int i = 0)
{
return instanceWrap_;
}
InstanceWrapper const&
getInstance(int i = 0) const
{
return instanceWrap_;
}
int
addInstance(StorePtr& s, WasmExternVec const& imports)
{
instanceWrap_ = {s, module_, imports, j_};
return 0;
}
std::int64_t
getGas() const
{
return instanceWrap_ ? instanceWrap_.getGas() : -1;
}
private:
static ModulePtr
init(StorePtr& s, Bytes const& wasmBin, beast::Journal j);
WasmExternVec
buildImports(StorePtr& s, ImportVec const& imports) const;
};
class WasmiEngine
{
EnginePtr engine_;
StorePtr store_;
std::unique_ptr<ModuleWrapper> moduleWrap_;
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
std::mutex m_; // 1 instance mutex
public:
WasmiEngine() : engine_(init()), store_(nullptr, &wasm_store_delete)
{
}
~WasmiEngine() = default;
static EnginePtr
init();
Expected<WasmResult<int32_t>, WasmTER>
run(Bytes const& wasmCode,
HostFunctions& hfs,
int64_t gas,
std::string_view funcName,
std::vector<WasmParam> const& params,
ImportVec const& imports,
beast::Journal j);
NotTEC
check(
Bytes const& wasmCode,
HostFunctions& hfs,
std::string_view funcName,
std::vector<WasmParam> const& params,
ImportVec const& imports,
beast::Journal j);
[[nodiscard]] std::int64_t
getGas() const
{
return moduleWrap_ ? moduleWrap_->getGas() : -1; // LCOV_EXCL_LINE
}
// Host functions helper functionality
wasm_trap_t*
newTrap(std::string const& msg);
// LCOV_EXCL_START
[[nodiscard]] beast::Journal
getJournal() const
{
return j_;
}
// LCOV_EXCL_STOP
private:
[[nodiscard]] InstanceWrapper&
getRT(int m = 0, int i = 0) const
{
if (!moduleWrap_)
Throw<std::runtime_error>("no module");
return moduleWrap_->getInstance(i);
}
[[nodiscard]] Wmem
getMem() const
{
return moduleWrap_ ? moduleWrap_->getMem() : Wmem();
}
Expected<WasmResult<int32_t>, WasmTER>
runHlp(
Bytes const& wasmCode,
HostFunctions& hfs,
int64_t gas,
std::string_view funcName,
std::vector<WasmParam> const& params,
ImportVec const& imports,
beast::Journal j);
NotTEC
checkHlp(
Bytes const& wasmCode,
HostFunctions& hfs,
std::string_view funcName,
std::vector<WasmParam> const& params,
ImportVec const& imports,
beast::Journal j);
int
addModule(Bytes const& wasmCode, bool instantiate, ImportVec const& imports, int64_t gas);
void
clearModules();
// int addInstance();
int32_t
runFunc(std::string_view const funcName, int32_t p);
int32_t
makeModule(Bytes const& wasmCode, WasmExternVec const& imports = {});
[[nodiscard]] FuncInfo
getFunc(std::string_view funcName) const
{
return moduleWrap_->getFunc(funcName);
}
static std::vector<wasm_val_t>
convertParams(std::vector<WasmParam> const& params);
static int
compareParamTypes(wasm_valtype_vec_t const* ftp, std::vector<wasm_val_t> const& p);
static void
addParam(std::vector<wasm_val_t>& in, int32_t p);
static void
addParam(std::vector<wasm_val_t>& in, int64_t p);
template <int NR, class... Types>
inline WasmiResult
call(std::string_view func, Types&&... args);
template <int NR, class... Types>
inline WasmiResult
call(FuncInfo const& f, Types&&... args);
template <int NR, class... Types>
inline WasmiResult
call(FuncInfo const& f, std::vector<wasm_val_t>& in);
template <int NR, class... Types>
inline WasmiResult
call(FuncInfo const& f, std::vector<wasm_val_t>& in, std::int32_t p, Types&&... args);
template <int NR, class... Types>
inline WasmiResult
call(FuncInfo const& f, std::vector<wasm_val_t>& in, std::int64_t p, Types&&... args);
template <int NR, class... Types>
inline WasmiResult
call(
FuncInfo const& f,
std::vector<wasm_val_t>& in,
uint8_t const* d,
int32_t sz,
Types&&... args);
template <int NR, class... Types>
inline WasmiResult
call(FuncInfo const& f, std::vector<wasm_val_t>& in, Bytes const& p, Types&&... args);
};
} // namespace xrpl

View File

@@ -0,0 +1,145 @@
#pragma once
#include <xrpl/basics/Expected.h>
#include <xrpl/tx/wasm/CodecEnvelope.h>
#include <xrpl/tx/wasm/detail/CodecTypeCode.h>
#include <xrpl/tx/wasm/detail/CodecTypeTrait.h>
#include <array>
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
namespace xrpl::wasm::detail {
// Sentinel that lets the registry's X-macro expansion end with a trailing
// comma: CodecList<Trait0, Trait1, CodecEnd>. Its `void` CodecType never
// matches a real type, so every table builder simply skips it.
struct CodecEnd
{
using CodecType = void;
};
template <typename Trait, typename T>
inline constexpr bool traitIsV = std::is_same_v<typename Trait::CodecType, T>;
template <typename T>
using DecodeFn = Expected<T, CodecError> (*)(CodecEnvelope const&);
template <typename T>
using EncodeFn = Expected<CodecEnvelope, CodecError> (*)(T const&);
// Highest registered version of T within List. Unregistered T ⇒ build error.
template <typename T, typename List, std::size_t... I>
consteval CodecVersion
maxVersionImpl(std::index_sequence<I...>)
{
auto v = CodecVersion{};
auto found = false;
([&] {
using Trait = std::tuple_element_t<I, List>;
if constexpr (traitIsV<Trait, T>)
{
found = true;
v = std::max(v, Trait::kVersion);
}
}(), ...);
if (!found)
{
xrpl::Throw<std::runtime_error>("wasm codec: no codec registered for this type");
}
return v;
}
template <typename T, typename List>
consteval CodecVersion
maxVersion()
{
return maxVersionImpl<T, List>(
std::make_index_sequence<std::tuple_size_v<List>>{});
}
template <typename T, typename List, std::size_t... I>
consteval auto
makeDecoderTableImpl(std::index_sequence<I...>)
{
constexpr auto n = maxVersion<T, List>() + 1;
auto table = std::array<DecodeFn<T>, n>{};
([&] {
using Trait = std::tuple_element_t<I, List>;
if constexpr (traitIsV<Trait, T>)
{
table[Trait::kVersion] = &Trait::decode;
}
}(), ...);
for (const auto& entry : table)
{
if (!entry)
{
xrpl::Throw<std::runtime_error>("wasm codec: decoder versions must be contiguous from 0");
}
}
return table;
}
template <typename T, typename List>
inline constexpr auto decoderTableV = makeDecoderTableImpl<T, List>(
std::make_index_sequence<std::tuple_size_v<List>>{});
template <typename T, typename List, std::size_t... I>
consteval auto
makeEncoderTableImpl(std::index_sequence<I...>)
{
constexpr auto n = maxVersion<T, List>() + 1;
auto table = std::array<EncodeFn<T>, n>{};
([&] {
using Trait = std::tuple_element_t<I, List>;
if constexpr (traitIsV<Trait, T>)
{
table[Trait::kVersion] = &Trait::encode;
}
}(), ...);
for (const auto& entry : table)
{
if (!entry)
{
xrpl::Throw<std::runtime_error>("wasm codec: encoder versions must be contiguous from 0");
}
}
return table;
}
template <typename T, typename List>
inline constexpr auto encoderTableV = makeEncoderTableImpl<T, List>(
std::make_index_sequence<std::tuple_size_v<List>>{});
template <typename T, typename List>
Expected<T, CodecError>
decodeImpl(CodecEnvelope const& env)
{
if (env.header.type_code != codecTypeCodeV<T>)
{
return Unexpected{CodecError::TypeMismatch};
}
auto const& table = decoderTableV<T, List>;
if (env.header.version >= table.size())
{
return Unexpected{CodecError::UnsupportedVersion};
}
return table[env.header.version](env);
}
template <typename T, typename List>
Expected<CodecEnvelope, CodecError>
encodeAt(CodecVersion wireVersion, T const& value)
{
auto const& table = encoderTableV<T, List>;
if (wireVersion >= table.size())
{
return Unexpected{CodecError::UnsupportedVersion};
}
return table[wireVersion](value);
}
} // namespace xrpl::wasm::detail

View File

@@ -0,0 +1,12 @@
#pragma once
#include <tuple>
namespace xrpl::wasm::detail {
template <typename... Traits>
struct CodecList {
using TupleType = std::tuple<Traits...>;
};
} // namespace xrpl::wasm::detail

View File

@@ -0,0 +1,18 @@
#pragma once
#include <xrpl/basics/Expected.h>
#include <xrpl/tx/wasm/CodecEnvelope.h>
namespace xrpl::wasm::detail {
// Maps a contract's declared codec revision to the per-type wire version to
// emit for `code`. This is the revision/amendment POLICY layer, kept separate
// from both the generic dispatch machinery and the concrete value codecs.
//
// Defined in src/libxrpl/tx/wasm/codecs/CodecRevisions.cpp alongside the
// concrete CodecTrait<N> revision maps. (Amendment gating via Rules is a
// deliberate TODO there — see the .cpp.)
Expected<CodecVersion, CodecError>
wireVersionFor(CodecVersion declaredRevision, CodecTypeCode code);
} // namespace xrpl::wasm::detail

View File

@@ -0,0 +1,18 @@
#pragma once
#include <xrpl/tx/wasm/CodecEnvelope.h>
namespace xrpl::wasm::detail {
template <CodecVersion Version>
struct CodecTrait;
// template <>
// struct CodecTrait<0> {
// static constexpr auto kVersion = CodecVersion{0};
// static constexpr auto versions = std::to_array<std::pair<CodecTypeCode, CodecVersion>>({
// {codecTypeCodeV<STNumber>, 0},
// });
// };
} // namespace xrpl::wasm::detail

View File

@@ -0,0 +1,21 @@
#pragma once
#include <xrpl/tx/wasm/CodecEnvelope.h>
namespace xrpl::wasm::detail {
template <typename T>
struct CodecTypeCodeOf;
// Defines a type trait for registering a type code for each
// type in the codec.
template <typename T>
inline constexpr CodecTypeCode codecTypeCodeV = CodecTypeCodeOf<T>::kCodecTypeCode;
#define XRPL_WASM_CODEC_TYPE_CODE(CppType, Code) \
template <> \
struct CodecTypeCodeOf<CppType> { \
static constexpr CodecTypeCode kCodecTypeCode = Code; \
};
} // namespace xrpl::wasm::detail

View File

@@ -0,0 +1,27 @@
#pragma once
#include <xrpl/tx/wasm/CodecEnvelope.h>
#include <limits>
namespace xrpl::wasm::detail {
// Defines a type trait for registering a specific type code and version
// encoding/decoding implementation for each type in the codec.
template <CodecTypeCode TypeCode, CodecVersion Version>
struct CodecTypeTrait;
#define XRPL_WASM_CODEC_TYPE_TRAIT(CppType, Version, PayloadSize) \
template <> \
struct CodecTypeTrait<codecTypeCodeV<CppType>, Version> { \
using CodecType = CppType; \
static constexpr auto kCodecTypeCode = codecTypeCodeV<CppType>; \
static constexpr auto kVersion = Version; \
static constexpr auto kPayloadSize = PayloadSize; \
static Expected<CodecType, CodecError> decode(CodecEnvelope const& env); \
static Expected<CodecEnvelope, CodecError> encode(CodecType const& value); \
};
inline constexpr auto kVariablePayloadSize = std::numeric_limits<CodecLength>::max();
} // namespace xrpl::wasm::detail

View File

@@ -0,0 +1,48 @@
//
// codec_revisions.macro — the SINGLE registration point for codec
// REVISIONS (the "contract-declared codec revision -> per-type wire version"
// pinning). Included multiple times with different macro definitions, so NO
// include guard.
//
// A revision fixes, for every type, which wire version a contract that declared
// that revision receives. It is deliberately SEPARATE from the (type, version)
// registry: adding a codec version does not change any revision — bumping a
// revision is its own act, tied to the amendment that enables it.
//
// CODEC_REVISION_BEGIN(Revision)
// CODEC_REVISION_ENTRY(CppType, Version) // repeated, one per type
// CODEC_REVISION_END(Revision)
//
// HOW TO ADD A REVISION (e.g. revision 1 that emits the widened Number v1):
// 1. Add a block here:
// CODEC_REVISION_BEGIN(1)
// CODEC_REVISION_ENTRY(Number, 1) // widened
// CODEC_REVISION_ENTRY(AccountID, 0) // unchanged
// CODEC_REVISION_END(1)
// 2. Gate it behind its amendment in CodecRevisions.cpp (see the
// amendment-gating TODO in wireVersionFor).
// Both CodecTrait<1> and the wireVersionFor switch case regenerate from this.
//
#ifndef CODEC_REVISION_BEGIN
#define CODEC_REVISION_BEGIN(Revision)
#endif
#ifndef CODEC_REVISION_ENTRY
#define CODEC_REVISION_ENTRY(CppType, Version)
#endif
#ifndef CODEC_REVISION_END
#define CODEC_REVISION_END(Revision)
#endif
// clang-format off
CODEC_REVISION_BEGIN(0)
CODEC_REVISION_ENTRY(Number, 0)
CODEC_REVISION_ENTRY(AccountID, 0)
CODEC_REVISION_END(0)
// clang-format on
#undef CODEC_REVISION_BEGIN
#undef CODEC_REVISION_ENTRY
#undef CODEC_REVISION_END

View File

@@ -0,0 +1,58 @@
//
// codec_types.macro — the SINGLE registration point for wasm codecs.
//
// This file is #included multiple times with different macro definitions, so
// it has NO include guard. Every codec is declared here exactly once, on two
// axes, and everything else (the trait tuple, the dispatch tables, the
// explicit instantiations, the type-code map) is generated from it — so those
// can never drift apart.
//
// CODEC_TYPE(CppType, TypeCode)
// Once per C++ type. Assigns its stable 16-bit wire type code. Drives the
// type-code map and the per-type explicit instantiations of decode/encode.
//
// CODEC_VERSION(CppType, Version, PayloadSize)
// Once per (type, version). Declares a CodecTypeTrait specialization and
// adds it to the registered-codec tuple. Versions per type MUST be
// contiguous from 0 (enforced at compile time).
//
// HOW TO ADD A CODEC
//
// A) A new VERSION of an existing type (e.g. widen Number):
// 1. Add a line here: CODEC_VERSION(Number, 1, 20)
// 2. Define its bodies in the type's .cpp (codecs/NumberCodec.cpp):
// CodecTypeTrait<codecTypeCodeV<Number>, 1>::decode/encode
// 3. If a codec revision should emit it, add an entry in
// codec_revisions.macro.
// (Versions must stay contiguous from 0, or the build fails.)
//
// B) A brand-new TYPE (e.g. STAmount):
// 1. CODEC_TYPE(STAmount, 0x0003) and CODEC_VERSION(STAmount, 0, N) here.
// 2. Add its C++ header include to codecs/CodecTypes.h.
// 3. Create codecs/STAmountCodec.cpp with the decode/encode bodies.
// (GLOB_RECURSE picks up the new .cpp automatically — no CMake edit.)
// 4. Add a CODEC_REVISION_ENTRY(STAmount, 0) to each relevant revision
// block in codec_revisions.macro.
//
#ifndef CODEC_TYPE
#define CODEC_TYPE(CppType, TypeCode)
#endif
#ifndef CODEC_VERSION
#define CODEC_VERSION(CppType, Version, PayloadSize)
#endif
// clang-format off
// C++ type wire type code
CODEC_TYPE(Number, 0x0001)
CODEC_TYPE(AccountID, 0x0002)
// C++ type version payload bytes
CODEC_VERSION(Number, 0, 12) // int64 mantissa + int32 exponent
CODEC_VERSION(AccountID, 0, 20) // 20 raw bytes
// clang-format on
#undef CODEC_TYPE
#undef CODEC_VERSION

175
package/README.md Normal file
View File

@@ -0,0 +1,175 @@
# Linux Packaging
This directory contains all files needed to build RPM and Debian packages for `xrpld`.
## Directory layout
```
package/
build_pkg.sh Staging and build script (called by CMake targets and CI)
rpm/
xrpld.spec RPM spec (xrpld_version/pkg_release passed via rpmbuild --define)
debian/ Debian control files (control, rules, install, links, conffiles, ...)
shared/
xrpld.service systemd unit file (used by both RPM and DEB)
xrpld.sysusers sysusers.d config (used by both RPM and DEB)
xrpld.tmpfiles tmpfiles.d config (used by both RPM and DEB)
xrpld.logrotate logrotate config (installed to /etc/logrotate.d/xrpld)
update-xrpld auto-update script (installed to /usr/libexec/xrpld/, run by update-xrpld.timer)
```
## Prerequisites
Packaging targets and their container images are declared in
[`.github/scripts/strategy-matrix/linux.json`](../.github/scripts/strategy-matrix/linux.json)
via a `"package": true` field on specific os entries. Today only
`linux/amd64` is emitted; the architecture is hardcoded in `generate.py`
and the workflow runner. The package format
(deb or rpm) is inferred at build time from the container's package manager
(`apt-get` -> deb, `dnf`/`yum` -> rpm). The image tag is composed as
`ghcr.io/xrplf/ci/{distro}-{version}:{compiler}-{cver}-sha-{image_sha}`
the same scheme used by `reusable-build-test.yml`. Bump `image_sha` in
`linux.json` and both CI and local builds pick up the new image with no
workflow edits.
| Package type | Image (derived from `linux.json`) | Tool required |
| ------------ | ---------------------------------------------------- | --------------------------------------------------------------- |
| RPM | `ghcr.io/xrplf/ci/rhel-9:gcc-12-sha-<git_sha>` | `rpmbuild` |
| DEB | `ghcr.io/xrplf/ci/ubuntu-jammy:gcc-12-sha-<git_sha>` | `dpkg-buildpackage`, `debhelper (>= 13)`, `dh-sequence-systemd` |
To print the exact image tags for the current `linux.json`:
```bash
./.github/scripts/strategy-matrix/generate.py --packaging --config=.github/scripts/strategy-matrix/linux.json
```
## Building packages
### Via CI
Caller workflows (`on-pr.yml`, `on-tag.yml`, `on-trigger.yml`) call
`reusable-strategy-matrix.yml` with `mode: packaging` to generate the matrix of
`{artifact_name, os}` entries, then fan out to
`reusable-package.yml` per entry. That workflow downloads the pre-built `xrpld`
binary artifact, detects the package format from the container, and calls
`build_pkg.sh` directly — no CMake configure or build step is needed inside
the packaging job.
### Locally (mirrors CI)
With an `xrpld` binary already built at `build/xrpld`, run the packaging step
inside the same container CI uses. The image tag is derived from `linux.json`
so you don't need to hardcode a SHA.
```bash
# From the repo root. Pick any image flagged with `"package": true` in
# linux.json; the package format is inferred from the container's package
# manager. Example for the rpm-producing image:
IMAGE=$(jq -r '
.os | map(select(.package == true))[0] |
"ghcr.io/xrplf/ci/\(.distro_name)-\(.distro_version):\(.compiler_name)-\(.compiler_version)-sha-\(.image_sha)"
' .github/scripts/strategy-matrix/linux.json)
VERSION=2.4.0-local
PKG_RELEASE=1
docker run --rm \
-v "$(pwd):/src" \
-w /src \
"$IMAGE" \
./package/build_pkg.sh --pkg-version "$VERSION" --pkg-release "$PKG_RELEASE"
# Output:
# build/debbuild/*.deb (DEB + dbgsym .ddeb)
# build/rpmbuild/RPMS/x86_64/*.rpm
```
### Via CMake (host-side target)
If you run CMake configure on a host that has `rpmbuild` or `dpkg-buildpackage`
installed natively, you can use the CMake target directly — no container
needed, but the host toolchain replaces the pinned CI image:
```bash
cmake \
-Dxrpld=ON \
-Dxrpld_version=2.4.0-local \
-Dtests=OFF \
..
cmake --build . --target package # deb on Debian/Ubuntu, rpm on RHEL
```
The `cmake/XrplPackaging.cmake` module defines the target only if at least one
of `rpmbuild` / `dpkg-buildpackage` is present; `build_pkg.sh` then infers the
package format from the host's package manager. The packaging script installs
to FHS-standard paths (`/usr/bin`, `/etc/xrpld`, etc.) regardless of
`CMAKE_INSTALL_PREFIX`.
## How `build_pkg.sh` works
`build_pkg.sh` accepts long-form flags, each of which can also be set via an
environment variable. Flags override env vars; env vars override the built-in
defaults. Run `./package/build_pkg.sh --help` for the same table:
| Flag | Env var | Default | Purpose |
| -------------------------- | ------------------- | ----------------------------- | ----------------------------------- |
| `--src-dir DIR` | `SRC_DIR` | `$PWD` | repo root |
| `--build-dir DIR` | `BUILD_DIR` | `$PWD/build` | directory holding pre-built `xrpld` |
| `--pkg-version STR` | `PKG_VERSION` | parsed from `xrpld --version` | version string, e.g. `3.2.0-b1` |
| `--pkg-release N` | `PKG_RELEASE` | `1` | package release number |
| `--source-date-epoch SECS` | `SOURCE_DATE_EPOCH` | latest git commit ctime | reproducibility timestamp |
The package format (`deb` or `rpm`) is inferred from the host's package
manager (`apt-get` -> deb, `dnf`/`yum` -> rpm). Hosts without one of those
fail early.
Flags are for explicit invocation; environment variables are intended for
CMake/systemd/CI integration. The CI workflow and the CMake `package` target
both invoke `build_pkg.sh` with no flags, configuring it entirely via env
(see `cmake/XrplPackaging.cmake`).
It resolves `SRC_DIR` and `BUILD_DIR` to absolute paths, then calls
`stage_common()` to copy the binary, config files, and shared support files
into the staging area, and invokes the platform build tool.
### RPM
1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory.
2. Copies `xrpld.spec` and all source files (binary, configs, service files) into `SOURCES/`.
3. Runs `rpmbuild -bb --define "xrpld_version ..." --define "pkg_release ..."`. The spec uses manual `install` commands to place files.
4. Output: `rpmbuild/RPMS/x86_64/xrpld-*.rpm`
### DEB
1. Creates a staging source tree at `debbuild/source/` inside the build directory.
2. Stages the binary, configs, `README.md`, and `LICENSE.md`.
3. Copies `package/debian/` control files into `debbuild/source/debian/`.
4. Copies shared service/sysusers/tmpfiles into `debian/` where `dh_installsystemd`, `dh_installsysusers`, and `dh_installtmpfiles` pick them up automatically.
5. Generates a minimal `debian/changelog` (pre-release versions use `~` instead of `-`).
6. Runs `dpkg-buildpackage -b --no-sign`. `debian/rules` uses manual `install` commands.
7. Output: `debbuild/*.deb` and `debbuild/*.ddeb` (dbgsym package)
## Post-build verification
```bash
# DEB
dpkg-deb -c debbuild/*.deb | grep -E 'systemd|sysusers|tmpfiles'
lintian -I debbuild/*.deb
# RPM
rpm -qlp rpmbuild/RPMS/x86_64/*.rpm
```
## Reproducibility
The following environment variables improve build reproducibility. They are not
set automatically by `build_pkg.sh`; set them manually if needed:
```bash
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
export TZ=UTC
export LC_ALL=C.UTF-8
export GZIP=-n
export DEB_BUILD_OPTIONS="noautodbgsym reproducible=+fixfilepath"
```

192
package/build_pkg.sh Executable file
View File

@@ -0,0 +1,192 @@
#!/usr/bin/env bash
set -euo pipefail
# Build an RPM or Debian package from a pre-built xrpld binary.
#
# Flags override env vars; env vars override defaults. Env vars are intended
# for CMake/systemd/CI integration; flags are for explicit invocation.
usage() {
cat <<'EOF'
Usage: build_pkg.sh [options]
Options (each can also be set via the env var shown):
--src-dir DIR repo root [SRC_DIR; default: $PWD]
--build-dir DIR directory holding xrpld [BUILD_DIR; default: $PWD/build]
--pkg-version STR version, e.g. 3.2.0-b1 [PKG_VERSION; default: parsed from xrpld --version]
--pkg-release N package release number [PKG_RELEASE; default: 1]
--source-date-epoch SECS reproducibility timestamp [SOURCE_DATE_EPOCH; default: latest git commit ctime]
-h, --help show this help and exit
EOF
}
need_arg() {
if [[ $# -lt 2 || "$2" == --* ]]; then
echo "Missing value for $1" >&2
exit 2
fi
}
# Seed from env. CLI parsing below overrides these directly.
SRC_DIR="${SRC_DIR:-}"
BUILD_DIR="${BUILD_DIR:-}"
PKG_VERSION="${PKG_VERSION:-}"
PKG_RELEASE="${PKG_RELEASE:-}"
SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-}"
while [[ $# -gt 0 ]]; do
case "$1" in
--src-dir) need_arg "$@"; SRC_DIR="$2"; shift 2 ;;
--build-dir) need_arg "$@"; BUILD_DIR="$2"; shift 2 ;;
--pkg-version) need_arg "$@"; PKG_VERSION="$2"; shift 2 ;;
--pkg-release) need_arg "$@"; PKG_RELEASE="$2"; shift 2 ;;
--source-date-epoch) need_arg "$@"; SOURCE_DATE_EPOCH="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*)
echo "Unknown argument: $1" >&2
usage >&2
exit 2
;;
esac
done
SRC_DIR="$(cd "${SRC_DIR:-${PWD}}" && pwd)"
BUILD_DIR="$(cd "${BUILD_DIR:-${PWD}/build}" && pwd)"
PKG_RELEASE="${PKG_RELEASE:-1}"
if [[ -z "${PKG_VERSION}" ]]; then
PKG_VERSION="$("${BUILD_DIR}/xrpld" --version | awk 'NR==1 {print $3; exit}')"
fi
if [[ -z "${PKG_VERSION}" ]]; then
echo "PKG_VERSION is empty (not provided and could not be derived)." >&2
exit 1
fi
VERSION="${PKG_VERSION}"
if command -v apt-get >/dev/null 2>&1; then
pkg_type=deb
elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
pkg_type=rpm
else
echo "Cannot infer pkg_type: no apt-get, dnf, or yum on PATH." >&2
exit 1
fi
if [[ -z "${SOURCE_DATE_EPOCH}" ]]; then
if git -C "$SRC_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
SOURCE_DATE_EPOCH="$(git -C "$SRC_DIR" log -1 --format=%ct)"
else
SOURCE_DATE_EPOCH="$(date +%s)"
fi
fi
export SOURCE_DATE_EPOCH
CHANGELOG_DATE="$(date -u -R -d "@$SOURCE_DATE_EPOCH")"
# Split VERSION at the first '-' into base and optional pre-release suffix.
# Examples: "3.2.0" -> ("3.2.0", ""); "3.2.0-b1" -> ("3.2.0", "b1").
VER_BASE="${VERSION%%-*}"
VER_SUFFIX="${VERSION#*-}"
[[ "${VER_SUFFIX}" == "${VERSION}" ]] && VER_SUFFIX=""
# Reject multi-segment suffixes (e.g. "beta-1", "rc1-15-gabc123"). The RPM
# Release field forbids '-', and the convention here is single-token suffixes
# like b1 or rc2. Fail early with a clear message rather than letting either
# rpmbuild blow up or silently mangling dashes into dots.
if [[ "${VER_SUFFIX}" == *-* ]]; then
echo "build_pkg.sh: multi-segment pre-release in VERSION='${VERSION}' (suffix '${VER_SUFFIX}')." >&2
echo "Use single-token suffixes like 3.2.0-b1 or 3.2.0-rc2." >&2
exit 1
fi
SHARED="${SRC_DIR}/package/shared"
DEBIAN_DIR="${SRC_DIR}/package/debian"
# Stage files that both packaging systems consume using the same filenames.
stage_common() {
local dest="$1"
mkdir -p "${dest}"
cp "${BUILD_DIR}/xrpld" "${dest}/xrpld"
cp "${SRC_DIR}/cfg/xrpld-example.cfg" "${dest}/xrpld.cfg"
cp "${SRC_DIR}/cfg/validators-example.txt" "${dest}/validators.txt"
cp "${SRC_DIR}/LICENSE.md" "${dest}/LICENSE.md"
cp "${SRC_DIR}/README.md" "${dest}/README.md"
cp "${SHARED}/xrpld.service" "${dest}/xrpld.service"
cp "${SHARED}/xrpld.sysusers" "${dest}/xrpld.sysusers"
cp "${SHARED}/xrpld.tmpfiles" "${dest}/xrpld.tmpfiles"
cp "${SHARED}/xrpld.logrotate" "${dest}/xrpld.logrotate"
cp "${SHARED}/update-xrpld" "${dest}/update-xrpld"
cp "${SHARED}/update-xrpld.service" "${dest}/update-xrpld.service"
cp "${SHARED}/update-xrpld.timer" "${dest}/update-xrpld.timer"
cp "${SHARED}/50-xrpld.preset" "${dest}/50-xrpld.preset"
}
build_rpm() {
local topdir="${BUILD_DIR}/rpmbuild"
rm -rf "${topdir}"
mkdir -p "${topdir}"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
cp "${SRC_DIR}/package/rpm/xrpld.spec" "${topdir}/SPECS/xrpld.spec"
stage_common "${topdir}/SOURCES"
# RPM Version can't contain '-'. A pre-release goes in Release with a
# leading "0." so 3.2.0-b1 sorts before the final 3.2.0-<pkg_release>.
local rpm_release="${PKG_RELEASE}"
[[ -n "${VER_SUFFIX}" ]] && rpm_release="0.${VER_SUFFIX}.${PKG_RELEASE}"
set -x
rpmbuild -bb \
--define "_topdir ${topdir}" \
--define "xrpld_version ${VER_BASE}" \
--define "xrpld_release ${rpm_release}" \
"${topdir}/SPECS/xrpld.spec"
}
build_deb() {
local staging="${BUILD_DIR}/debbuild/source"
rm -rf "${staging}"
mkdir -p "${staging}"
stage_common "${staging}"
cp -r "${DEBIAN_DIR}" "${staging}/debian"
# Debhelper auto-discovers these only from debian/.
cp "${staging}/xrpld.service" "${staging}/debian/xrpld.service"
cp "${staging}/xrpld.sysusers" "${staging}/debian/xrpld.sysusers"
cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles"
cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate"
cp "${staging}/update-xrpld.service" "${staging}/debian/xrpld.update-xrpld.service"
cp "${staging}/update-xrpld.timer" "${staging}/debian/xrpld.update-xrpld.timer"
# Debian '~' marks a pre-release; 3.2.0~b1 sorts before 3.2.0.
local deb_full_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}-${PKG_RELEASE}"
# Derive release channel from the version suffix:
# (none) -> stable (tagged release)
# b0 -> develop (develop-branch build)
# b<N>, rc<N> -> unstable (pre-release)
local deb_distribution
case "${VER_SUFFIX}" in
"") deb_distribution="stable" ;;
b0) deb_distribution="develop" ;;
*) deb_distribution="unstable" ;;
esac
cat > "${staging}/debian/changelog" <<EOF
xrpld (${deb_full_version}) ${deb_distribution}; urgency=medium
* Release ${VERSION}.
-- XRPL Foundation <contact@xrplf.org> ${CHANGELOG_DATE}
EOF
chmod +x "${staging}/debian/rules"
set -x
( cd "${staging}" && dpkg-buildpackage -b --no-sign -d )
}
"build_${pkg_type}"

23
package/debian/control Normal file
View File

@@ -0,0 +1,23 @@
Source: xrpld
Section: net
Priority: optional
Maintainer: XRPL Foundation <contact@xrplf.org>
Rules-Requires-Root: no
Build-Depends:
debhelper-compat (= 13)
Standards-Version: 4.7.0
Homepage: https://github.com/XRPLF/rippled
Vcs-Git: https://github.com/XRPLF/rippled.git
Vcs-Browser: https://github.com/XRPLF/rippled
Package: xrpld
Section: net
Priority: optional
Architecture: any
Depends:
${shlibs:Depends},
${misc:Depends}
Description: XRP Ledger daemon
Reference implementation of the XRP Ledger protocol.
Participates in the peer-to-peer network, processes transactions,
and maintains a local ledger copy.

18
package/debian/copyright Normal file
View File

@@ -0,0 +1,18 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: rippled
Source: https://github.com/XRPLF/rippled
Files: *
Copyright: 2011-present, the XRP Ledger developers
License: ISC
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

27
package/debian/rules Normal file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/make -f
export DH_VERBOSE = 1
%:
dh $@
override_dh_auto_configure override_dh_auto_build override_dh_auto_test:
@:
override_dh_installsystemd:
dh_installsystemd --no-start xrpld.service
dh_installsystemd --name=update-xrpld --no-start update-xrpld.service update-xrpld.timer
execute_before_dh_installtmpfiles:
dh_installsysusers
override_dh_installsysusers:
override_dh_install:
install -D -m 0755 xrpld debian/xrpld/usr/bin/xrpld
install -D -m 0644 xrpld.cfg debian/xrpld/etc/xrpld/xrpld.cfg
install -D -m 0644 validators.txt debian/xrpld/etc/xrpld/validators.txt
install -D -m 0755 update-xrpld debian/xrpld/usr/libexec/xrpld/update-xrpld
override_dh_dwz:
@:

View File

@@ -0,0 +1 @@
3.0 (quilt)

View File

@@ -0,0 +1,2 @@
README.md
LICENSE.md

View File

@@ -0,0 +1,2 @@
# Legacy compat symlinks (remove next major release)
usr/bin/xrpld usr/local/bin/rippled

100
package/rpm/xrpld.spec Normal file
View File

@@ -0,0 +1,100 @@
Name: xrpld
Version: %{xrpld_version}
Release: %{xrpld_release}%{?dist}
Summary: XRP Ledger daemon
License: ISC
URL: https://github.com/XRPLF/rippled
ExclusiveArch: x86_64 aarch64
BuildRequires: systemd-rpm-macros
%undefine _debugsource_packages
%debug_package
%build_mtime_policy clamp_to_source_date_epoch
%{?systemd_requires}
%{?sysusers_requires_compat}
%description
xrpld is the reference implementation of the XRP Ledger protocol. It
participates in the peer-to-peer XRP Ledger network, processes
transactions, and maintains the ledger database.
%prep
:
%build
:
%install
install -Dm0755 %{_sourcedir}/xrpld %{buildroot}%{_bindir}/%{name}
install -Dm0644 %{_sourcedir}/xrpld.cfg %{buildroot}%{_sysconfdir}/%{name}/xrpld.cfg
install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{name}/validators.txt
# systemd units, sysusers, tmpfiles, preset
install -Dm0644 %{_sourcedir}/xrpld.service %{buildroot}%{_unitdir}/xrpld.service
install -Dm0644 %{_sourcedir}/update-xrpld.service %{buildroot}%{_unitdir}/update-xrpld.service
install -Dm0644 %{_sourcedir}/update-xrpld.timer %{buildroot}%{_unitdir}/update-xrpld.timer
install -Dm0644 %{_sourcedir}/xrpld.sysusers %{buildroot}%{_sysusersdir}/xrpld.conf
install -Dm0644 %{_sourcedir}/xrpld.tmpfiles %{buildroot}%{_tmpfilesdir}/xrpld.conf
install -Dm0644 %{_sourcedir}/50-xrpld.preset %{buildroot}%{_presetdir}/50-xrpld.preset
# Logrotate config
install -Dm0644 %{_sourcedir}/xrpld.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/%{name}
# Update helper
install -Dm0755 %{_sourcedir}/update-xrpld %{buildroot}%{_libexecdir}/%{name}/update-xrpld
# Docs
install -Dm0644 %{_sourcedir}/LICENSE.md %{buildroot}%{_docdir}/%{name}/LICENSE.md
install -Dm0644 %{_sourcedir}/README.md %{buildroot}%{_docdir}/%{name}/README.md
# Legacy compatibility for pre-FHS package layouts.
# TODO: remove after rippled fully deprecated.
install -d %{buildroot}/usr/local/bin
ln -s %{_bindir}/%{name} %{buildroot}/usr/local/bin/rippled
%pre
%sysusers_create_package %{name} %{_sourcedir}/xrpld.sysusers
%post
systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
%systemd_post xrpld.service update-xrpld.timer
%preun
%systemd_preun xrpld.service update-xrpld.timer
%postun
%systemd_postun_with_restart xrpld.service
%files
%license %{_docdir}/%{name}/LICENSE.md
%doc %{_docdir}/%{name}/README.md
%dir %{_sysconfdir}/%{name}
%dir %{_libexecdir}/%{name}
%{_bindir}/%{name}
%config(noreplace) %{_sysconfdir}/%{name}/xrpld.cfg
%config(noreplace) %{_sysconfdir}/%{name}/validators.txt
%config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
%{_libexecdir}/%{name}/update-xrpld
%{_unitdir}/xrpld.service
%{_unitdir}/update-xrpld.service
%{_unitdir}/update-xrpld.timer
%{_presetdir}/50-xrpld.preset
%{_sysusersdir}/xrpld.conf
%{_tmpfilesdir}/xrpld.conf
%ghost %dir /var/lib/%{name}
%ghost %dir /var/log/%{name}
# Legacy compatibility for pre-FHS package layouts.
# TODO: remove after rippled fully deprecated.
/usr/local/bin/rippled

View File

@@ -0,0 +1,4 @@
# /usr/lib/systemd/system-preset/50-xrpld.preset
enable xrpld.service
# Don't enable automatic updates
disable update-xrpld.timer

152
package/shared/update-xrpld Executable file
View File

@@ -0,0 +1,152 @@
#!/usr/bin/env bash
set -euo pipefail
# Optional: also write logs to a legacy file in addition to journald.
# By default, this script logs to systemd/journald, viewable via:
# journalctl -t update-xrpld
#
# Uncomment the line below if you need a flat file for compatibility with
# external tooling, manual inspection, or environments where journald logs
# are not persisted or easily accessible.
#
# Note: This duplicates all output (stdout/stderr) to both journald and the file.
# It is generally not needed on modern systems and may cause log file growth
# if left enabled long-term.
#
# Requires /var/log/xrpld/ to exist and be writable by the service (root).
#
# exec > >(tee -a /var/log/xrpld/update.log) 2>&1
PATH=/usr/sbin:/usr/bin:/sbin:/bin
PKG_NAME=${PKG_NAME:-xrpld}
log() {
# If running under systemd/journald, let it handle timestamps.
if [[ -n "${JOURNAL_STREAM:-}" ]]; then
printf '%s\n' "$*"
else
printf '%s %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"
fi
}
require_root() {
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
log "RESULT: failed reason=not-root"
exit 1
fi
}
get_installed_version() {
if command -v dpkg-query >/dev/null 2>&1; then
dpkg-query -W -f='${Version}' "$PKG_NAME" 2>/dev/null || printf 'unknown'
elif command -v rpm >/dev/null 2>&1; then
rpm -q --qf '%{VERSION}-%{RELEASE}' "$PKG_NAME" 2>/dev/null || printf 'unknown'
else
printf 'unknown'
fi
}
trap 'log "RESULT: failed reason=script-error exit_code=$?"' ERR
apt_can_update() {
apt-get update -qq
apt-get -s --only-upgrade install "$PKG_NAME" 2>/dev/null | grep -q "^Inst ${PKG_NAME}\b"
}
apt_apply_update() {
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
-o Dpkg::Options::="--force-confdef" \
-o Dpkg::Options::="--force-confold" \
"$PKG_NAME"
}
get_rpm_pm() {
if command -v dnf >/dev/null 2>&1; then
printf 'dnf\n'
elif command -v yum >/dev/null 2>&1; then
printf 'yum\n'
else
return 1
fi
}
rpm_refresh_metadata() {
local pm=$1
if [[ "$pm" == "dnf" ]]; then
dnf makecache --refresh -q >/dev/null
else
yum clean expire-cache -q >/dev/null
fi
}
rpm_can_update() {
local pm=$1
rpm_refresh_metadata "$pm"
local rc=0
set +e
"$pm" check-update -q "$PKG_NAME" >/dev/null 2>&1
rc=$?
set -e
if [[ $rc -eq 100 ]]; then
return 0
elif [[ $rc -eq 0 ]]; then
return 1
else
log "$pm check-update failed with exit code ${rc}."
exit 1
fi
}
rpm_apply_update() {
local pm=$1
"$pm" update -y "$PKG_NAME"
}
restart_service() {
# Preserve the operator's prior service state: if xrpld was intentionally
# stopped before the update, don't bring it back up just because the
# auto-update timer fired.
if systemctl is-active --quiet "${PKG_NAME}.service"; then
systemctl restart "${PKG_NAME}.service"
log "${PKG_NAME} service restarted successfully."
else
log "${PKG_NAME} service was not running; skipping restart to preserve prior state."
fi
}
main() {
require_root
if command -v apt-get >/dev/null 2>&1; then
log "Checking for ${PKG_NAME} updates via apt"
if apt_can_update; then
log "Update available; installing."
apt_apply_update
restart_service
log "RESULT: updated ${PKG_NAME}=$(get_installed_version)"
else
log "RESULT: no-update ${PKG_NAME}=$(get_installed_version)"
fi
return
fi
local rpm_pm=""
if rpm_pm="$(get_rpm_pm)"; then
log "Checking for ${PKG_NAME} updates via ${rpm_pm}"
if rpm_can_update "$rpm_pm"; then
log "Update available; installing"
rpm_apply_update "$rpm_pm"
restart_service
log "RESULT: updated ${PKG_NAME}=$(get_installed_version)"
else
log "RESULT: no-update ${PKG_NAME}=$(get_installed_version)"
fi
return
fi
log "RESULT: failed reason=no-package-manager"
exit 1
}
main "$@"

View File

@@ -0,0 +1,16 @@
[Unit]
Description=Check for and install xrpld package updates
Documentation=man:systemd.service(5)
Wants=network-online.target
After=network-online.target
ConditionPathExists=/usr/libexec/xrpld/update-xrpld
ConditionPathExists=/usr/bin/xrpld
[Service]
Type=oneshot
ExecStart=/usr/bin/flock -n /run/lock/xrpld-update.lock /usr/libexec/xrpld/update-xrpld
StandardOutput=journal
StandardError=journal
SyslogIdentifier=update-xrpld
TimeoutStartSec=30min
PrivateTmp=true

View File

@@ -0,0 +1,10 @@
[Unit]
Description=Daily xrpld update check
[Timer]
OnCalendar=*-*-* 00:00:00
RandomizedDelaySec=24h
Persistent=true
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,19 @@
/var/log/xrpld/*.log {
daily
minsize 200M
rotate 7
nocreate
missingok
notifempty
compress
compresscmd /usr/bin/gzip
compressext .gz
postrotate
# Only signal the daemon if it's actually running; otherwise the RPC
# call returns a transport error and logrotate marks the rotation as
# failed, generating recurring errors on stopped nodes.
if systemctl is-active --quiet xrpld; then
/usr/bin/xrpld --conf /etc/xrpld/xrpld.cfg logrotate
fi
endscript
}

View File

@@ -0,0 +1,22 @@
[Unit]
Description=XRP Ledger Daemon
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=simple
ExecStart=/usr/bin/xrpld --net --silent --conf /etc/xrpld/xrpld.cfg
Restart=always
RestartSec=5s
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true
PrivateTmp=true
User=xrpld
Group=xrpld
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1 @@
u xrpld - "XRP Ledger daemon" /var/lib/xrpld /sbin/nologin

View File

@@ -0,0 +1,2 @@
d /var/lib/xrpld 0750 xrpld xrpld -
d /var/log/xrpld 0750 xrpld xrpld -

View File

@@ -70,8 +70,8 @@ isVaultPseudoAccountFrozen(
return false; // zero MPToken won't block deletion of MPTokenIssuance
auto const issuer = mptIssuance->getAccountID(sfIssuer);
auto const mptIssuer = AccountRoot(issuer, view);
if (!mptIssuer.exists())
auto const mptIssuer = view.read(keylet::account(issuer));
if (mptIssuer == nullptr)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null MPToken issuer");
@@ -348,11 +348,11 @@ withdrawToDestExceedsLimit(
return amount.asset().visit(
[&](Issue const& issue) -> TER {
auto const iou = IOUIssuance(view, issue);
auto const owed = iou.creditBalance(to);
auto const& currency = issue.currency;
auto const owed = creditBalance(view, to, issuer, currency);
if (owed <= beast::kZero)
{
auto const limit = iou.creditLimit(to);
auto const limit = creditLimit(view, to, issuer, currency);
if (-owed >= limit || amount > (limit + owed))
return tecNO_LINE;
}
@@ -366,17 +366,17 @@ canWithdraw(
ReadView const& view,
AccountID const& from,
AccountID const& to,
RAccountRoot const& toWrapped,
SLE::const_ref toSle,
STAmount const& amount,
bool hasDestinationTag)
{
if (auto const ret = toWrapped.checkDestinationAndTag(hasDestinationTag))
if (auto const ret = checkDestinationAndTag(toSle, hasDestinationTag))
return ret;
if (from == to)
return tesSUCCESS;
if (toWrapped->isFlag(lsfDepositAuth))
if (toSle->isFlag(lsfDepositAuth))
{
if (!view.exists(keylet::depositPreauth(to, from)))
return tecNO_PERMISSION;
@@ -393,9 +393,9 @@ canWithdraw(
STAmount const& amount,
bool hasDestinationTag)
{
auto const toWrapped = AccountRoot(to, view);
auto const toSle = view.read(keylet::account(to));
return canWithdraw(view, from, to, toWrapped, amount, hasDestinationTag);
return canWithdraw(view, from, to, toSle, amount, hasDestinationTag);
}
[[nodiscard]] TER
@@ -427,8 +427,8 @@ doWithdraw(
}
else
{
auto dst = AccountRoot(dstAcct, view, j);
if (auto err = verifyDepositPreauth(tx, view, senderAcct, dst, j))
auto dstSle = view.read(keylet::account(dstAcct));
if (auto err = verifyDepositPreauth(tx, view, senderAcct, dstAcct, dstSle, j))
return err;
}

View File

@@ -12,8 +12,6 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/Sandbox.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/TokenHelpers.h>
#include <xrpl/protocol/AMMCore.h>
@@ -529,7 +527,7 @@ ammLPHolds(
<< " lpAccount=" << to_string(lpAccount)
<< " amount=" << amount.getFullText();
}
else if (IOUIssuance(view, ammAccount, currency).isFrozen(lpAccount))
else if (isFrozen(view, lpAccount, currency, ammAccount))
{
amount.clear(Issue{currency, ammAccount});
JLOG(j.trace()) << "ammLPHolds: frozen currency "
@@ -601,20 +599,20 @@ ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const
return asset.visit(
[&](MPTIssue const& issue) {
if (auto const sle = view.read(keylet::mptoken(issue, ammAccountID));
sle && !MPTokenIssuance(view, issue).isFrozen(ammAccountID))
sle && !isFrozen(view, ammAccountID, issue))
return STAmount{issue, (*sle)[sfMPTAmount]};
return STAmount{asset};
},
[&](Issue const& issue) {
if (isXRP(issue))
{
if (AccountRoot const acct(ammAccountID, view); acct)
return (*acct)[sfBalance];
if (auto const sle = view.read(keylet::account(ammAccountID)))
return (*sle)[sfBalance];
}
else if (
auto const sle =
view.read(keylet::line(ammAccountID, issue.account, issue.currency));
sle && !IOUIssuance(view, issue).isFrozen(ammAccountID))
sle && !isFrozen(view, ammAccountID, issue.currency, issue.account))
{
STAmount amount = (*sle)[sfBalance];
if (ammAccountID > issue.account)
@@ -724,8 +722,8 @@ deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Jo
}
auto const ammAccountID = (*ammSle)[sfAccount];
WAccountRoot ammRoot(ammAccountID, sb, j);
if (!ammRoot)
auto sleAMMRoot = sb.peek(keylet::account(ammAccountID));
if (!sleAMMRoot)
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMAccount: AMM account does not exist "
@@ -762,7 +760,7 @@ deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Jo
}
sb.erase(ammSle);
ammRoot.erase();
sb.erase(sleAMMRoot);
return tesSUCCESS;
}

View File

@@ -31,13 +31,14 @@
namespace xrpl {
template <typename ViewT>
bool
AccountRoot<ViewT>::isGlobalFrozen() const
isGlobalFrozen(ReadView const& view, AccountID const& issuer)
{
if (!this->exists())
if (isXRP(issuer))
return false;
return this->sle_->isFlag(lsfGlobalFreeze);
if (auto const sle = view.read(keylet::account(issuer)))
return sle->isFlag(lsfGlobalFreeze);
return false;
}
// An owner count cannot be negative. If adjustment would cause a negative
@@ -82,62 +83,66 @@ confineOwnerCount(
return adjusted;
}
template <typename ViewT>
XRPAmount
AccountRoot<ViewT>::xrpLiquid(std::int32_t ownerCountAdj) const
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j)
{
if (!this->exists())
auto const sle = view.read(keylet::account(id));
if (sle == nullptr)
return beast::kZero;
// Return balance minus reserve
std::uint32_t const ownerCount = confineOwnerCount(
this->readView().ownerCountHook(id_, this->sle_->getFieldU32(sfOwnerCount)), ownerCountAdj);
std::uint32_t const ownerCount =
confineOwnerCount(view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
// Pseudo-accounts have no reserve requirement
auto const reserve =
this->isPseudoAccount() ? XRPAmount{0} : this->readView().fees().accountReserve(ownerCount);
isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount);
auto const fullBalance = this->sle_->getFieldAmount(sfBalance);
auto const fullBalance = sle->getFieldAmount(sfBalance);
auto const balance = this->readView().balanceHookIOU(id_, xrpAccount(), fullBalance);
auto const balance = view.balanceHookIOU(id, xrpAccount(), fullBalance);
STAmount const amount = (balance < reserve) ? STAmount{0} : balance - reserve;
JLOG(this->j_.trace()) << "accountHolds:" << " account=" << to_string(id_)
<< " amount=" << amount.getFullText()
<< " fullBalance=" << fullBalance.getFullText()
<< " balance=" << balance.getFullText() << " reserve=" << reserve
<< " ownerCount=" << ownerCount << " ownerCountAdj=" << ownerCountAdj;
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id)
<< " amount=" << amount.getFullText()
<< " fullBalance=" << fullBalance.getFullText()
<< " balance=" << balance.getFullText() << " reserve=" << reserve
<< " ownerCount=" << ownerCount << " ownerCountAdj=" << ownerCountAdj;
return amount.xrp();
}
template <typename ViewT>
Rate
AccountRoot<ViewT>::transferRate() const
transferRate(ReadView const& view, AccountID const& issuer)
{
if (this->sle_ && this->sle_->isFieldPresent(sfTransferRate))
return Rate{this->sle_->getFieldU32(sfTransferRate)};
auto const sle = view.read(keylet::account(issuer));
if (sle && sle->isFieldPresent(sfTransferRate))
return Rate{sle->getFieldU32(sfTransferRate)};
return kParityRate;
}
template <typename ViewT>
void
AccountRoot<ViewT>::adjustOwnerCount(std::int32_t amount)
requires kIsWritable
adjustOwnerCount(
ApplyView& view,
std::shared_ptr<SLE> const& sle,
std::int32_t amount,
beast::Journal j)
{
XRPL_ASSERT(this->canModify(), "xrpl::adjustOwnerCount : can modify");
if (!sle)
return;
XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
std::uint32_t const current{this->sle_->getFieldU32(sfOwnerCount)};
AccountID const id = (*this->sle_)[sfAccount];
std::uint32_t const adjusted = confineOwnerCount(current, amount, id, this->j_);
this->applyView().adjustOwnerCountHook(id_, current, adjusted);
this->sle_->at(sfOwnerCount) = adjusted;
this->update();
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);
}
[[nodiscard]] AccountID
AccountID
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey)
{
// This number must not be changed without an amendment
@@ -186,18 +191,19 @@ getPseudoAccountFields()
return kPseudoFields;
}
template <typename ViewT>
[[nodiscard]] bool
AccountRoot<ViewT>::isPseudoAccount(std::set<SField const*> const& pseudoFieldFilter) const
isPseudoAccount(
std::shared_ptr<SLE const> sleAcct,
std::set<SField const*> const& pseudoFieldFilter)
{
auto const& fields = getPseudoAccountFields();
// Intentionally use defensive coding here because it's cheap and makes the
// semantics of true return value clean.
return this->sle_ && this->sle_->getType() == ltACCOUNT_ROOT &&
return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT &&
std::count_if(
fields.begin(), fields.end(), [this, &pseudoFieldFilter](SField const* sf) -> bool {
return this->sle_->isFieldPresent(*sf) &&
fields.begin(), fields.end(), [&sleAcct, &pseudoFieldFilter](SField const* sf) -> bool {
return sleAcct->isFieldPresent(*sf) &&
(pseudoFieldFilter.empty() || pseudoFieldFilter.contains(sf));
}) > 0;
}
@@ -244,23 +250,18 @@ createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const
return account;
}
template <typename ViewT>
[[nodiscard]] TER
AccountRoot<ViewT>::checkDestinationAndTag(bool hasDestinationTag) const
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
{
if (this->sle_ == nullptr)
if (toSle == nullptr)
return tecNO_DST;
// The tag is basically account-specific information we don't
// understand, but we can require someone to fill it in.
if (this->sle_->isFlag(lsfRequireDestTag) && !hasDestinationTag)
if (toSle->isFlag(lsfRequireDestTag) && !hasDestinationTag)
return tecDST_TAG_NEEDED; // Cannot send without a tag
return tesSUCCESS;
}
// Explicit template instantiations
template class AccountRoot<ReadView>;
template class AccountRoot<ApplyView>;
} // namespace xrpl

View File

@@ -78,8 +78,8 @@ deleteSLE(ApplyView& view, std::shared_ptr<SLE> const& sleCredential, beast::Jou
auto delSLE = [&view, &sleCredential, j](
AccountID const& account, SField const& node, bool isOwner) -> TER {
WAccountRoot wrappedAccount(account, view, j);
if (!wrappedAccount)
auto const sleAccount = view.peek(keylet::account(account));
if (!sleAccount)
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
@@ -98,7 +98,7 @@ deleteSLE(ApplyView& view, std::shared_ptr<SLE> const& sleCredential, beast::Jou
}
if (isOwner)
wrappedAccount.adjustOwnerCount(-1);
adjustOwnerCount(view, sleAccount, -1, j);
return tesSUCCESS;
};
@@ -351,7 +351,8 @@ verifyDepositPreauth(
STTx const& tx,
ApplyView& view,
AccountID const& src,
RAccountRoot const& dst,
AccountID const& dst,
std::shared_ptr<SLE const> const& sleDst,
beast::Journal j)
{
// If depositPreauth is enabled, then an account that requires
@@ -372,15 +373,15 @@ verifyDepositPreauth(
return tecEXPIRED;
}
if (dst.exists() && ((dst->getFlags() & lsfDepositAuth) != 0u))
if (sleDst && sleDst->isFlag(lsfDepositAuth))
{
if (src != dst)
{
if (!view.exists(keylet::depositPreauth(dst.id(), src)))
if (!view.exists(keylet::depositPreauth(dst, src)))
{
return !credentialsPresent ? tecNO_PERMISSION
: credentials::authorizedDepositPreauth(
view, tx.getFieldV256(sfCredentialIDs), dst.id());
view, tx.getFieldV256(sfCredentialIDs), dst);
}
}
}

View File

@@ -9,7 +9,6 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -25,9 +24,6 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <optional>
#include <utility>
namespace xrpl {
@@ -2054,68 +2050,4 @@ loanMakePayment(
"xrpl::loanMakePayment : fee paid is valid");
return totalParts;
}
// 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
confineOwnerCount(
std::uint32_t current,
std::int32_t adjustment,
std::optional<AccountID> const& id = std::nullopt,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
{
std::uint32_t adjusted{current + adjustment};
if (adjustment > 0)
{
// Overflow is well defined on unsigned
if (adjusted < current)
{
if (id)
{
JLOG(j.fatal()) << "Account " << *id << " owner count exceeds max!";
}
adjusted = std::numeric_limits<std::uint32_t>::max();
}
}
else
{
// Underflow is well defined on unsigned
if (adjusted > current)
{
if (id)
{
JLOG(j.fatal()) << "Account " << *id << " owner count set below 0!";
}
adjusted = 0;
XRPL_ASSERT(!id, "xrpl::confineOwnerCount : id is not set");
}
}
return adjusted;
}
void
adjustOwnerCount(
std::shared_ptr<SLE> const& sle,
ApplyView& view,
std::int32_t amount,
beast::Journal j)
{
if (!sle)
return;
// This function is only used for LoanBrokers, so assert that
// AccountRoot should use WAccountRoot.adjustOwnerCount instead
XRPL_ASSERT(sle->getType() == ltLOAN_BROKER, "xrpl::adjustOwnerCount : sle is loan broker");
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);
}
} // namespace xrpl

View File

@@ -11,7 +11,6 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
@@ -39,65 +38,64 @@
namespace xrpl {
template <typename ViewT>
bool
MPTokenIssuance<ViewT>::isGlobalFrozen() const
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
{
if (this->exists())
return this->sle_->isFlag(lsfMPTLocked);
return false;
}
template <typename ViewT>
bool
MPTokenIssuance<ViewT>::isIndividualFrozen(AccountID const& account) const
{
if (auto const sle = this->readView().read(keylet::mptoken(mptID_, account)))
if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())))
return sle->isFlag(lsfMPTLocked);
return false;
}
template <typename ViewT>
bool
MPTokenIssuance<ViewT>::isFrozen(AccountID const& account, int depth) const
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
{
return isGlobalFrozen() || isIndividualFrozen(account) ||
isVaultPseudoAccountFrozen(this->readView(), account, mptIssue_, depth);
if (auto const sle = view.read(keylet::mptoken(mptIssue.getMptID(), account)))
return sle->isFlag(lsfMPTLocked);
return false;
}
template <typename ViewT>
[[nodiscard]] bool
MPTokenIssuance<ViewT>::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth)
const
bool
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth)
{
if (isGlobalFrozen())
return isGlobalFrozen(view, mptIssue) || isIndividualFrozen(view, account, mptIssue) ||
isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
}
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
MPTIssue const& mptIssue,
int depth)
{
if (isGlobalFrozen(view, mptIssue))
return true;
for (auto const& account : accounts)
{
if (isIndividualFrozen(account))
if (isIndividualFrozen(view, account, mptIssue))
return true;
}
for (auto const& account : accounts)
{
if (isVaultPseudoAccountFrozen(this->readView(), account, mptIssue_, depth))
if (isVaultPseudoAccountFrozen(view, account, mptIssue, depth))
return true;
}
return false;
}
template <typename ViewT>
Rate
MPTokenIssuance<ViewT>::transferRate() const
transferRate(ReadView const& view, MPTID const& issuanceID)
{
// fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
// For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
// which represents 50% of 1,000,000,000
if (this->exists() && this->sle_->isFieldPresent(sfTransferFee))
if (auto const sle = view.read(keylet::mptIssuance(issuanceID));
sle && sle->isFieldPresent(sfTransferFee))
{
auto const fee = this->sle_->getFieldU16(sfTransferFee);
auto const fee = sle->getFieldU16(sfTransferFee);
XRPL_ASSERT(fee <= kMaxTransferFee, "xrpl::transferRate : fee is too large");
return Rate{1'000'000'000u + (10'000 * fee)};
}
@@ -105,15 +103,16 @@ MPTokenIssuance<ViewT>::transferRate() const
return kParityRate;
}
template <typename ViewT>
[[nodiscard]] TER
MPTokenIssuance<ViewT>::canAddHolding() const
canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
{
if (!this->exists())
auto mptID = mptIssue.getMptID();
auto issuance = view.read(keylet::mptIssuance(mptID));
if (!issuance)
{
return tecOBJECT_NOT_FOUND;
}
if (!this->sle_->isFlag(lsfMPTCanTransfer))
if (!issuance->isFlag(lsfMPTCanTransfer))
{
return tecNO_AUTH;
}
@@ -121,39 +120,40 @@ MPTokenIssuance<ViewT>::canAddHolding() const
return tesSUCCESS;
}
template <typename ViewT>
[[nodiscard]] TER
MPTokenIssuance<ViewT>::addEmptyHolding(
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
beast::Journal journal)
requires kIsWritable
{
if (!this->canModify())
auto const& mptID = mptIssue.getMptID();
auto const mpt = view.peek(keylet::mptIssuance(mptID));
if (!mpt)
return tefINTERNAL; // LCOV_EXCL_LINE
if (this->sle_->isFlag(lsfMPTLocked))
if (mpt->isFlag(lsfMPTLocked))
return tefINTERNAL; // LCOV_EXCL_LINE
if (this->applyView().peek(keylet::mptoken(mptID_, accountID)))
if (view.peek(keylet::mptoken(mptID, accountID)))
return tecDUPLICATE;
if (accountID == getIssuer())
if (accountID == mptIssue.getIssuer())
return tesSUCCESS;
return authorizeMPToken(priorBalance, accountID, journal);
return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
}
template <typename ViewT>
[[nodiscard]] TER
MPTokenIssuance<ViewT>::authorizeMPToken(
authorizeMPToken(
ApplyView& view,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
beast::Journal journal,
std::uint32_t flags,
std::optional<AccountID> holderID)
requires kIsWritable
{
ApplyView& view = this->applyView();
WAccountRoot wrappedAcct(account, view, journal);
if (!wrappedAcct)
auto const sleAcct = view.peek(keylet::account(account));
if (!sleAcct)
return tecINTERNAL; // LCOV_EXCL_LINE
// If the account that submitted the tx is a holder
@@ -166,7 +166,7 @@ MPTokenIssuance<ViewT>::authorizeMPToken(
// - delete the MPToken
if ((flags & tfMPTUnauthorize) != 0u)
{
auto const mptokenKey = keylet::mptoken(mptID_, account);
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto const sleMpt = view.peek(mptokenKey);
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0 ||
(view.rules().enabled(fixCleanup3_1_3) &&
@@ -177,7 +177,7 @@ MPTokenIssuance<ViewT>::authorizeMPToken(
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tecINTERNAL; // LCOV_EXCL_LINE
wrappedAcct.adjustOwnerCount(-1);
adjustOwnerCount(view, sleAcct, -1, journal);
view.erase(sleMpt);
return tesSUCCESS;
@@ -192,7 +192,7 @@ MPTokenIssuance<ViewT>::authorizeMPToken(
// 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 = wrappedAcct->getFieldU32(sfOwnerCount);
std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
XRPAmount const reserveCreate(
(uOwnerCount < 2) ? XRPAmount(beast::kZero)
: view.fees().accountReserve(uOwnerCount + 1));
@@ -201,7 +201,8 @@ MPTokenIssuance<ViewT>::authorizeMPToken(
return tecINSUFFICIENT_RESERVE;
// Defensive check before we attempt to create MPToken for the issuer
if (!this->sle_ || this->sle_->getAccountID(sfIssuer) == account)
auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
if (!mpt || mpt->getAccountID(sfIssuer) == account)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token");
@@ -210,32 +211,33 @@ MPTokenIssuance<ViewT>::authorizeMPToken(
// LCOV_EXCL_STOP
}
auto const mptokenKey = keylet::mptoken(mptID_, account);
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto mptoken = std::make_shared<SLE>(mptokenKey);
if (auto ter = dirLink(view, account, mptoken))
return ter; // LCOV_EXCL_LINE
(*mptoken)[sfAccount] = account;
(*mptoken)[sfMPTokenIssuanceID] = mptID_;
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
(*mptoken)[sfFlags] = 0;
view.insert(mptoken);
// Update owner count.
wrappedAcct.adjustOwnerCount(1);
adjustOwnerCount(view, sleAcct, 1, journal);
return tesSUCCESS;
}
if (!this->sle_)
auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
if (!sleMptIssuance)
return tecINTERNAL; // LCOV_EXCL_LINE
// If the account that submitted this tx is the issuer of the MPT
// Note: `account_` is issuer's account
// `holderID` is holder's account
if (account != (*this->sle_)[sfIssuer])
if (account != (*sleMptIssuance)[sfIssuer])
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleMpt = view.peek(keylet::mptoken(mptID_, *holderID));
auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
if (!sleMpt)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -262,17 +264,19 @@ MPTokenIssuance<ViewT>::authorizeMPToken(
return tesSUCCESS;
}
template <typename ViewT>
[[nodiscard]] TER
MPTokenIssuance<ViewT>::removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
requires kIsWritable
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal)
{
ApplyView& view = this->applyView();
// If the account is the issuer, then no token should exist. MPTs do not
// have the legacy ability to create such a situation, but check anyway. If
// a token does exist, it will get deleted. If not, return success.
bool const accountIsIssuer = accountID == getIssuer();
auto const mptoken = view.peek(keylet::mptoken(mptID_, accountID));
bool const accountIsIssuer = accountID == mptIssue.getIssuer();
auto const& mptID = mptIssue.getMptID();
auto const mptoken = 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
@@ -284,27 +288,35 @@ MPTokenIssuance<ViewT>::removeEmptyHolding(AccountID const& accountID, beast::Jo
return tecHAS_OBLIGATIONS;
return authorizeMPToken(
view,
{}, // priorBalance
mptID,
accountID,
journal,
tfMPTUnauthorize // flags
);
}
template <typename ViewT>
[[nodiscard]] TER
MPTokenIssuance<ViewT>::requireAuth(AccountID const& account, AuthType authType, int depth) const
requireAuth(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& account,
AuthType authType,
int depth)
{
if (!this->exists())
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto const sleIssuance = view.read(mptID);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
auto const mptIssuer = AccountRoot(this->sle_->getAccountID(sfIssuer), this->readView());
auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
// issuer is always "authorized"
if (mptIssuer == account) // Issuer won't have MPToken
return tesSUCCESS;
bool const featureSAVEnabled = this->readView().rules().enabled(featureSingleAssetVault);
bool const featureSAVEnabled = view.rules().enabled(featureSingleAssetVault);
if (featureSAVEnabled)
{
@@ -312,32 +324,29 @@ MPTokenIssuance<ViewT>::requireAuth(AccountID const& account, AuthType authType,
return tecINTERNAL; // LCOV_EXCL_LINE
// requireAuth is recursive if the issuer is a vault pseudo-account
if (!mptIssuer.exists())
auto const sleIssuer = view.read(keylet::account(mptIssuer));
if (!sleIssuer)
return tefINTERNAL; // LCOV_EXCL_LINE
if (mptIssuer->isFieldPresent(sfVaultID))
if (sleIssuer->isFieldPresent(sfVaultID))
{
auto const sleVault =
this->readView().read(keylet::vault(mptIssuer->getFieldH256(sfVaultID)));
auto const sleVault = view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
if (!sleVault)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const asset = sleVault->at(sfAsset);
if (auto const err = asset.visit(
[&](Issue const& issue) {
return IOUIssuance(this->readView(), issue).requireAuth(account, authType);
},
[&](Issue const& issue) { return requireAuth(view, issue, account, authType); },
[&](MPTIssue const& issue) {
return MPTokenIssuance<ReadView>(this->readView(), issue)
.requireAuth(account, authType, depth + 1);
return requireAuth(view, issue, account, authType, depth + 1);
});
!isTesSuccess(err))
return err;
}
}
auto const mptokenID = keylet::mptoken(mptID_, account);
auto const sleToken = this->readView().read(mptokenID);
auto const mptokenID = keylet::mptoken(mptID.key, account);
auto const sleToken = view.read(mptokenID);
// if account has no MPToken, fail
if (!sleToken && (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
@@ -345,14 +354,14 @@ MPTokenIssuance<ViewT>::requireAuth(AccountID const& account, AuthType authType,
// Note, this check is not amendment-gated because DomainID will be always
// empty **unless** writing to it has been enabled by an amendment
auto const maybeDomainID = this->sle_->at(~sfDomainID);
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
if (maybeDomainID)
{
XRPL_ASSERT(
this->sle_->isFlag(lsfMPTRequireAuth),
sleIssuance->isFlag(lsfMPTRequireAuth),
"xrpl::requireAuth : issuance requires authorization");
// ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
auto const ter = credentials::validDomain(this->readView(), *maybeDomainID, account);
auto const ter = credentials::validDomain(view, *maybeDomainID, account);
if (isTesSuccess(ter))
{
return ter; // Note: sleToken might be null
@@ -368,46 +377,47 @@ MPTokenIssuance<ViewT>::requireAuth(AccountID const& account, AuthType authType,
if (featureSAVEnabled)
{
// Implicitly authorize Vault and LoanBroker pseudo-accounts
AccountRoot const accountRoot(account, this->readView());
if (accountRoot.isPseudoAccount({&sfVaultID, &sfLoanBrokerID}))
if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID}))
return tesSUCCESS;
}
// mptoken must be authorized if issuance enabled requireAuth
if (this->sle_->isFlag(lsfMPTRequireAuth) && (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
(!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
return tecNO_AUTH;
return tesSUCCESS; // Note: sleToken might be null
}
template <typename ViewT>
[[nodiscard]] TER
MPTokenIssuance<ViewT>::enforceMPTokenAuthorization(
enforceMPTokenAuthorization(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance, // for MPToken authorization
beast::Journal j)
requires kIsWritable
{
if (!this->canModify())
auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
if (!sleIssuance)
return tefINTERNAL; // LCOV_EXCL_LINE
XRPL_ASSERT(
this->sle_->isFlag(lsfMPTRequireAuth),
sleIssuance->isFlag(lsfMPTRequireAuth),
"xrpl::enforceMPTokenAuthorization : authorization required");
if (account == this->sle_->at(sfIssuer))
if (account == sleIssuance->at(sfIssuer))
return tefINTERNAL; // LCOV_EXCL_LINE
auto const keylet = keylet::mptoken(mptID_, account);
auto const sleToken = this->readView().read(keylet); // NOTE: might be null
auto const maybeDomainID = this->sle_->at(~sfDomainID);
auto const keylet = keylet::mptoken(mptIssuanceID, account);
auto const sleToken = view.read(keylet); // NOTE: might be null
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
bool expired = false;
bool const authorizedByDomain = [&]() -> bool {
// NOTE: defensive here, should be checked in preclaim
if (!static_cast<bool>(maybeDomainID))
if (!maybeDomainID.has_value())
return false; // LCOV_EXCL_LINE
auto const ter = verifyValidDomain(this->applyView(), account, *maybeDomainID, j);
auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
if (isTesSuccess(ter))
return true;
if (ter == tecEXPIRED)
@@ -426,7 +436,7 @@ MPTokenIssuance<ViewT>::enforceMPTokenAuthorization(
// Either way, return tecNO_AUTH and there is nothing else to do
return expired ? tecEXPIRED : tecNO_AUTH;
}
if (!authorizedByDomain && static_cast<bool>(maybeDomainID))
if (!authorizedByDomain && maybeDomainID.has_value())
{
// Found an MPToken but the account is not authorized and we expect
// it to have been authorized by the domain. This could be because the
@@ -438,7 +448,7 @@ MPTokenIssuance<ViewT>::enforceMPTokenAuthorization(
// We found an MPToken, but sfDomainID is not set, so this is a classic
// MPToken which requires authorization by the token issuer.
XRPL_ASSERT(
sleToken != nullptr && !static_cast<bool>(maybeDomainID),
sleToken != nullptr && !maybeDomainID.has_value(),
"xrpl::enforceMPTokenAuthorization : found MPToken");
if (sleToken->isFlag(lsfMPTAuthorized))
return tesSUCCESS;
@@ -450,7 +460,7 @@ MPTokenIssuance<ViewT>::enforceMPTokenAuthorization(
// Found an MPToken, authorized by the domain. Ignore authorization flag
// lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
XRPL_ASSERT(
static_cast<bool>(maybeDomainID),
maybeDomainID.has_value(),
"xrpl::enforceMPTokenAuthorization : found MPToken for domain");
return tesSUCCESS;
}
@@ -459,11 +469,13 @@ MPTokenIssuance<ViewT>::enforceMPTokenAuthorization(
// Could not find MPToken but there should be one because we are
// authorized by domain. Proceed to create it, then return tesSUCCESS
XRPL_ASSERT(
static_cast<bool>(maybeDomainID) && sleToken == nullptr,
maybeDomainID.has_value() && sleToken == nullptr,
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
if (auto const err = authorizeMPToken(
priorBalance, // priorBalance
account, // account
view,
priorBalance, // priorBalance
mptIssuanceID, // mptIssuanceID
account, // account
j);
!isTesSuccess(err))
return err;
@@ -477,39 +489,26 @@ MPTokenIssuance<ViewT>::enforceMPTokenAuthorization(
// LCOV_EXCL_STOP
}
template <typename ViewT>
TER
MPTokenIssuance<ViewT>::canTransfer(AccountID const& from, AccountID const& to) const
canTransfer(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& from,
AccountID const& to)
{
if (!this->exists())
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto const sleIssuance = view.read(mptID);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
if (!this->sle_->isFlag(lsfMPTCanTransfer))
if (!sleIssuance->isFlag(lsfMPTCanTransfer))
{
if (from != (*this->sle_)[sfIssuer] && to != (*this->sle_)[sfIssuer])
if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
return TER{tecNO_AUTH};
}
return tesSUCCESS;
}
template <typename ViewT>
bool
MPTokenIssuance<ViewT>::requiresAuth() const
{
if (!this->exists())
return false;
return this->sle_->isFlag(lsfMPTRequireAuth);
}
template <typename ViewT>
bool
MPTokenIssuance<ViewT>::canClawback() const
{
if (!this->exists())
return false;
return this->sle_->isFlag(lsfMPTCanClawback);
}
TER
canTrade(ReadView const& view, Asset const& asset)
{
@@ -525,17 +524,13 @@ canTrade(ReadView const& view, Asset const& asset)
});
}
template <typename ViewT>
TER
MPTokenIssuance<ViewT>::lockEscrow(
AccountID const& sender,
STAmount const& amount,
beast::Journal j)
requires kIsWritable
lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount, beast::Journal j)
{
ApplyView& view = this->applyView();
auto const& mptIssue = mptIssue_;
if (!this->exists())
auto const mptIssue = amount.get<MPTIssue>();
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
{ // LCOV_EXCL_START
JLOG(j.error()) << "lockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
return tecOBJECT_NOT_FOUND;
@@ -550,7 +545,7 @@ MPTokenIssuance<ViewT>::lockEscrow(
// 1. Decrease the MPT Holder MPTAmount
// 2. Increase the MPT Holder EscrowedAmount
{
auto const mptokenID = keylet::mptoken(mptID_, sender);
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto sle = view.peek(mptokenID);
if (!sle)
{ // LCOV_EXCL_START
@@ -596,7 +591,7 @@ MPTokenIssuance<ViewT>::lockEscrow(
// 1. Increase the Issuance EscrowedAmount
// 2. DO NOT change the Issuance OutstandingAmount
{
uint64_t const issuanceEscrowed = (*this->sle_)[~sfLockedAmount].valueOr(0);
uint64_t const issuanceEscrowed = (*sleIssuance)[~sfLockedAmount].valueOr(0);
auto const pay = amount.mpt().value();
// Overflow check for addition
@@ -608,39 +603,39 @@ MPTokenIssuance<ViewT>::lockEscrow(
return tecINTERNAL;
} // LCOV_EXCL_STOP
if (this->sle_->isFieldPresent(sfLockedAmount))
if (sleIssuance->isFieldPresent(sfLockedAmount))
{
(*this->sle_)[sfLockedAmount] += pay;
(*sleIssuance)[sfLockedAmount] += pay;
}
else
{
this->sle_->setFieldU64(sfLockedAmount, pay);
sleIssuance->setFieldU64(sfLockedAmount, pay);
}
this->update();
view.update(sleIssuance);
}
return tesSUCCESS;
}
template <typename ViewT>
TER
MPTokenIssuance<ViewT>::unlockEscrow(
unlockEscrowMPT(
ApplyView& view,
AccountID const& sender,
AccountID const& receiver,
STAmount const& netAmount,
STAmount const& grossAmount,
beast::Journal j)
requires kIsWritable
{
ApplyView& view = this->applyView();
if (!view.rules().enabled(fixTokenEscrowV1))
{
XRPL_ASSERT(netAmount == grossAmount, "xrpl::unlockEscrowMPT : netAmount == grossAmount");
}
auto const& issuer = netAmount.getIssuer();
auto const& mptIssue = mptIssue_;
if (!this->exists())
auto const& mptIssue = netAmount.get<MPTIssue>();
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
{ // LCOV_EXCL_START
JLOG(j.error()) << "unlockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
return tecOBJECT_NOT_FOUND;
@@ -648,14 +643,14 @@ MPTokenIssuance<ViewT>::unlockEscrow(
// Decrease the Issuance EscrowedAmount
{
if (!this->sle_->isFieldPresent(sfLockedAmount))
if (!sleIssuance->isFieldPresent(sfLockedAmount))
{ // LCOV_EXCL_START
JLOG(j.error()) << "unlockEscrowMPT: no locked amount in issuance for "
<< mptIssue.getMptID();
return tecINTERNAL;
} // LCOV_EXCL_STOP
auto const locked = this->sle_->getFieldU64(sfLockedAmount);
auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
auto const redeem = grossAmount.mpt().value();
// Underflow check for subtraction
@@ -669,19 +664,19 @@ MPTokenIssuance<ViewT>::unlockEscrow(
auto const newLocked = locked - redeem;
if (newLocked == 0)
{
this->sle_->makeFieldAbsent(sfLockedAmount);
sleIssuance->makeFieldAbsent(sfLockedAmount);
}
else
{
this->sle_->setFieldU64(sfLockedAmount, newLocked);
sleIssuance->setFieldU64(sfLockedAmount, newLocked);
}
this->update();
view.update(sleIssuance);
}
if (issuer != receiver)
{
// Increase the MPT Holder MPTAmount
auto const mptokenID = keylet::mptoken(mptID_, receiver);
auto const mptokenID = keylet::mptoken(mptID.key, receiver);
auto sle = view.peek(mptokenID);
if (!sle)
{ // LCOV_EXCL_START
@@ -706,7 +701,7 @@ MPTokenIssuance<ViewT>::unlockEscrow(
else
{
// Decrease the Issuance OutstandingAmount
auto const outstanding = this->sle_->getFieldU64(sfOutstandingAmount);
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
auto const redeem = netAmount.mpt().value();
// Underflow check for subtraction
@@ -717,8 +712,8 @@ MPTokenIssuance<ViewT>::unlockEscrow(
return tecINTERNAL;
} // LCOV_EXCL_STOP
this->sle_->setFieldU64(sfOutstandingAmount, outstanding - redeem);
this->update();
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
view.update(sleIssuance);
}
if (issuer == sender)
@@ -728,7 +723,7 @@ MPTokenIssuance<ViewT>::unlockEscrow(
return tecINTERNAL;
} // LCOV_EXCL_STOP
// Decrease the MPT Holder EscrowedAmount
auto const mptokenID = keylet::mptoken(mptID_, sender);
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto sle = view.peek(mptokenID);
if (!sle)
{ // LCOV_EXCL_START
@@ -771,7 +766,7 @@ MPTokenIssuance<ViewT>::unlockEscrow(
auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
if (diff != 0)
{
auto const outstanding = this->sle_->getFieldU64(sfOutstandingAmount);
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
// Underflow check for subtraction
if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
{ // LCOV_EXCL_START
@@ -780,19 +775,20 @@ MPTokenIssuance<ViewT>::unlockEscrow(
return tecINTERNAL;
} // LCOV_EXCL_STOP
this->sle_->setFieldU64(sfOutstandingAmount, outstanding - diff);
this->update();
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
view.update(sleIssuance);
}
return tesSUCCESS;
}
template <typename ViewT>
TER
MPTokenIssuance<ViewT>::createMPToken(AccountID const& account, std::uint32_t flags)
requires kIsWritable
createMPToken(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
std::uint32_t const flags)
{
ApplyView& view = this->applyView();
auto const mptokenKey = keylet::mptoken(mptID_, account);
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto const ownerNode =
view.dirInsert(keylet::ownerDir(account), mptokenKey, describeOwnerDir(account));
@@ -802,7 +798,7 @@ MPTokenIssuance<ViewT>::createMPToken(AccountID const& account, std::uint32_t fl
auto mptoken = std::make_shared<SLE>(mptokenKey);
(*mptoken)[sfAccount] = account;
(*mptoken)[sfMPTokenIssuanceID] = mptID_;
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
(*mptoken)[sfFlags] = flags;
(*mptoken)[sfOwnerNode] = *ownerNode;
@@ -811,28 +807,31 @@ MPTokenIssuance<ViewT>::createMPToken(AccountID const& account, std::uint32_t fl
return tesSUCCESS;
}
template <typename ViewT>
TER
MPTokenIssuance<ViewT>::checkCreateMPT(AccountID const& holder, beast::Journal j)
requires kIsWritable
checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
beast::Journal j)
{
ApplyView& view = this->applyView();
if (getIssuer() == holder)
if (mptIssue.getIssuer() == holder)
return tesSUCCESS;
auto const mptokenID = keylet::mptoken(mptID_, holder);
auto const mptIssuanceID = keylet::mptIssuance(mptIssue.getMptID());
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
if (!view.exists(mptokenID))
{
if (auto const err = createMPToken(holder, 0); !isTesSuccess(err))
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0);
!isTesSuccess(err))
{
return err;
}
auto acct = WAccountRoot(holder, view, j);
if (!acct)
auto const sleAcct = view.peek(keylet::account(holder));
if (!sleAcct)
{
return tecINTERNAL;
}
acct.adjustOwnerCount(1);
adjustOwnerCount(view, sleAcct, 1, j);
}
return tesSUCCESS;
}
@@ -851,22 +850,13 @@ availableMPTAmount(SLE const& sleIssuance)
return max - outstanding;
}
template <typename ViewT>
std::int64_t
MPTokenIssuance<ViewT>::maxAmount() const
availableMPTAmount(ReadView const& view, MPTID const& mptID)
{
if (!this->exists())
auto const sle = view.read(keylet::mptIssuance(mptID));
if (!sle)
Throw<std::runtime_error>(transHuman(tecINTERNAL));
return maxMPTAmount(*this->sle_);
}
template <typename ViewT>
std::int64_t
MPTokenIssuance<ViewT>::availableAmount() const
{
if (!this->exists())
Throw<std::runtime_error>(transHuman(tecINTERNAL));
return availableMPTAmount(*this->sle_);
return availableMPTAmount(*sle);
}
bool
@@ -882,41 +872,23 @@ isMPTOverflow(
return (sendAmount > maximumAmount || outstandingAmount > (limit - sendAmount));
}
template <typename ViewT>
STAmount
MPTokenIssuance<ViewT>::issuerFundsToSelfIssue() const
issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue)
{
STAmount amount{mptIssue_};
STAmount amount{issue};
if (!this->exists())
auto const sle = view.read(keylet::mptIssuance(issue));
if (!sle)
return amount;
auto const available = availableMPTAmount(*this->sle_);
return this->readView().balanceHookSelfIssueMPT(mptIssue_, available);
auto const available = availableMPTAmount(*sle);
return view.balanceHookSelfIssueMPT(issue, available);
}
template <typename ViewT>
void
MPTokenIssuance<ViewT>::issuerSelfDebitHook(std::uint64_t amount)
requires kIsWritable
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount)
{
auto const available = availableAmount();
this->applyView().issuerSelfDebitHookMPT(mptIssue_, amount, available);
}
[[nodiscard]] TER
deleteAMMMPToken(
ApplyView& view,
std::shared_ptr<SLE> sleMpt,
AccountID const& ammAccountID,
beast::Journal j)
{
if (!view.dirRemove(
keylet::ownerDir(ammAccountID), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tefBAD_LEDGER; // LCOV_EXCL_LINE
view.erase(sleMpt);
return tesSUCCESS;
auto const available = availableMPTAmount(view, issue);
view.issuerSelfDebitHookMPT(issue, amount, available);
}
static TER
@@ -980,8 +952,4 @@ checkMPTTxAllowed(
return checkMPTAllowed(view, txType, asset, accountID);
}
// Explicit template instantiations
template class MPTokenIssuance<ReadView>;
template class MPTokenIssuance<ApplyView>;
} // namespace xrpl

View File

@@ -4,7 +4,6 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
@@ -270,8 +269,11 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft)
// the NFT.
std::shared_ptr<SLE> const page =
getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) {
WAccountRoot wrappedOwner(owner, view);
wrappedOwner.adjustOwnerCount(1);
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
1,
beast::Journal{beast::Journal::getNullSink()});
});
if (!page)
@@ -423,8 +425,11 @@ removeToken(
if (cnt != 0)
{
WAccountRoot wrappedOwner(owner, view);
wrappedOwner.adjustOwnerCount(cnt);
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
cnt,
beast::Journal{beast::Journal::getNullSink()});
}
return tesSUCCESS;
@@ -459,8 +464,11 @@ removeToken(
curr->makeFieldAbsent(sfPreviousPageMin);
}
WAccountRoot wrappedOwner(owner, view);
wrappedOwner.adjustOwnerCount(-1);
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
-1,
beast::Journal{beast::Journal::getNullSink()});
view.update(curr);
view.erase(prev);
@@ -515,8 +523,11 @@ removeToken(
view.peek(Keylet(ltNFTOKEN_PAGE, next->key()))))
cnt++;
WAccountRoot wrappedOwner(owner, view);
wrappedOwner.adjustOwnerCount(-1 * cnt);
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
-1 * cnt,
beast::Journal{beast::Journal::getNullSink()});
return tesSUCCESS;
}
@@ -632,8 +643,8 @@ deleteTokenOffer(ApplyView& view, std::shared_ptr<SLE> const& offer)
false))
return false;
WAccountRoot wrappedOwner(owner, view);
wrappedOwner.adjustOwnerCount(-1);
adjustOwnerCount(
view, view.peek(keylet::account(owner)), -1, beast::Journal{beast::Journal::getNullSink()});
view.erase(offer);
return true;
@@ -840,20 +851,20 @@ tokenOfferCreatePreclaim(
return tecNO_LINE;
}
if (IOUIssuance(view, amount.get<Issue>()).isFrozen(nftIssuer))
if (isFrozen(view, nftIssuer, amount.get<Issue>().currency, amount.getIssuer()))
return tecFROZEN;
}
if (nftIssuer != acctID && ((nftFlags & nft::kFlagTransferable) == 0))
{
AccountRoot const acctRoot(nftIssuer, view);
XRPL_ASSERT(acctRoot, "xrpl::nft::tokenOfferCreatePreclaim : non-null account");
auto const root = view.read(keylet::account(nftIssuer));
XRPL_ASSERT(root, "xrpl::nft::tokenOfferCreatePreclaim : non-null account");
if (auto minter = acctRoot->at(~sfNFTokenMinter); minter != acctID)
if (auto minter = (*root)[~sfNFTokenMinter]; minter != acctID)
return tefNFTOKEN_IS_NOT_TRANSFERABLE;
}
if (IOUIssuance(view, amount.get<Issue>()).isFrozen(acctID))
if (isFrozen(view, acctID, amount.get<Issue>().currency, amount.getIssuer()))
return tecFROZEN;
// If this is an offer to buy the token, the account must have the
@@ -871,26 +882,26 @@ tokenOfferCreatePreclaim(
{
// If a destination is specified, the destination must already be in
// the ledger.
AccountRoot const acctDst(*dest, view);
auto const sleDst = view.read(keylet::account(*dest));
if (!acctDst)
if (!sleDst)
return tecNO_DST;
// check if the destination has disallowed incoming offers
if ((acctDst->getFlags() & lsfDisallowIncomingNFTokenOffer) != 0u)
if (sleDst->isFlag(lsfDisallowIncomingNFTokenOffer))
return tecNO_PERMISSION;
}
if (owner)
{
AccountRoot const acctOwner(*owner, view);
auto const sleOwner = view.read(keylet::account(*owner));
// defensively check
// it should not be possible to specify owner that doesn't exist
if (!acctOwner)
if (!sleOwner)
return tecNO_TARGET;
if ((acctOwner->getFlags() & lsfDisallowIncomingNFTokenOffer) != 0u)
if (sleOwner->isFlag(lsfDisallowIncomingNFTokenOffer))
return tecNO_PERMISSION;
}
@@ -922,8 +933,9 @@ tokenOfferCreateApply(
beast::Journal j,
std::uint32_t txFlags)
{
AccountRoot const acctRoot(acctID, view);
if (priorBalance < view.fees().accountReserve(acctRoot->getFieldU32(sfOwnerCount) + 1))
Keylet const acctKeylet = keylet::account(acctID);
if (auto const acct = view.read(acctKeylet);
priorBalance < view.fees().accountReserve((*acct)[sfOwnerCount] + 1))
return tecINSUFFICIENT_RESERVE;
auto const offerID = keylet::nftoffer(acctID, seqProxy.value());
@@ -975,8 +987,7 @@ tokenOfferCreateApply(
}
// Update owner count.
WAccountRoot wrappedOwner(acctID, view);
wrappedOwner.adjustOwnerCount(1);
adjustOwnerCount(view, view.peek(acctKeylet), 1, j);
return tesSUCCESS;
}
@@ -993,8 +1004,8 @@ checkTrustlineAuthorized(
if (view.rules().enabled(fixEnforceNFTokenTrustlineV2))
{
AccountRoot const acctIssuer(issue.account, view);
if (!acctIssuer)
auto const issuerAccount = view.read(keylet::account(issue.account));
if (!issuerAccount)
{
JLOG(j.debug()) << "xrpl::nft::checkTrustlineAuthorized: can't "
"receive IOUs from non-existent issuer: "
@@ -1011,7 +1022,7 @@ checkTrustlineAuthorized(
return tesSUCCESS;
}
if (acctIssuer->isFlag(lsfRequireAuth))
if (issuerAccount->isFlag(lsfRequireAuth))
{
auto const trustLine = view.read(keylet::line(id, issue.account, issue.currency));
@@ -1045,8 +1056,8 @@ checkTrustlineDeepFrozen(
if (view.rules().enabled(featureDeepFreeze))
{
AccountRoot const acctIssuer(issue.account, view);
if (!acctIssuer)
auto const issuerAccount = view.read(keylet::account(issue.account));
if (!issuerAccount)
{
JLOG(j.debug()) << "xrpl::nft::checkTrustlineDeepFrozen: can't "
"receive IOUs from non-existent issuer: "

View File

@@ -57,8 +57,7 @@ offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
}
}
WAccountRoot wrappedOwner(owner, view, j);
wrappedOwner.adjustOwnerCount(-1);
adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
view.erase(sle);

View File

@@ -50,16 +50,15 @@ closeChannel(
}
// Transfer amount back to owner, decrement owner count
WAccountRoot wrappedOwner(src, view, j);
if (!wrappedOwner)
auto const sle = view.peek(keylet::account(src));
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
XRPL_ASSERT(
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
(*wrappedOwner)[sfBalance] =
(*wrappedOwner)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
wrappedOwner.adjustOwnerCount(-1);
wrappedOwner.update();
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
adjustOwnerCount(view, sle, -1, j);
view.update(sle);
// Remove PayChan from ledger
view.erase(slep);

View File

@@ -38,14 +38,16 @@ namespace xrpl {
//
//------------------------------------------------------------------------------
template <typename ViewT>
STAmount
IOUIssuance<ViewT>::creditLimit(AccountID const& account) const
creditLimit(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency)
{
auto const& issuer = this->id();
STAmount result(Issue{currency_, account});
STAmount result(Issue{currency, account});
auto sleRippleState = this->readView().read(keylet::line(account, issuer, currency_));
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
if (sleRippleState)
{
@@ -55,27 +57,28 @@ IOUIssuance<ViewT>::creditLimit(AccountID const& account) const
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditLimit : result issuer match");
XRPL_ASSERT(
result.get<Issue>().currency == currency_,
result.get<Issue>().currency == currency,
"xrpl::creditLimit : result currency "
"match");
return result;
}
template <typename ViewT>
IOUAmount
IOUIssuance<ViewT>::creditLimit2(AccountID const& account) const
creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur)
{
return toAmount<IOUAmount>(creditLimit(account));
return toAmount<IOUAmount>(creditLimit(v, acc, iss, cur));
}
template <typename ViewT>
STAmount
IOUIssuance<ViewT>::creditBalance(AccountID const& account) const
creditBalance(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency)
{
auto const& issuer = this->id();
STAmount result(Issue{currency_, account});
STAmount result(Issue{currency, account});
auto sleRippleState = this->readView().read(keylet::line(account, issuer, currency_));
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
if (sleRippleState)
{
@@ -87,7 +90,7 @@ IOUIssuance<ViewT>::creditBalance(AccountID const& account) const
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditBalance : result issuer match");
XRPL_ASSERT(
result.get<Issue>().currency == currency_,
result.get<Issue>().currency == currency,
"xrpl::creditBalance : result currency "
"match");
return result;
@@ -99,35 +102,19 @@ IOUIssuance<ViewT>::creditBalance(AccountID const& account) const
//
//------------------------------------------------------------------------------
template <typename ViewT>
bool
IOUIssuance<ViewT>::requiresAuth() const
isIndividualFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer)
{
if (!this->exists())
if (isXRP(currency))
return false;
return this->sle_->isFlag(lsfRequireAuth);
}
template <typename ViewT>
bool
IOUIssuance<ViewT>::canClawback() const
{
if (!this->exists())
return false;
return this->sle_->isFlag(lsfAllowTrustLineClawback);
}
template <typename ViewT>
bool
IOUIssuance<ViewT>::isIndividualFrozen(AccountID const& account) const
{
if (isXRP(currency_))
return false;
auto const& issuer = this->id();
if (issuer != account)
{
// Check if the issuer froze the line
auto const sle = this->readView().read(keylet::line(account, issuer, currency_));
auto const sle = view.read(keylet::line(account, issuer, currency));
if (sle && sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
return true;
}
@@ -136,41 +123,46 @@ IOUIssuance<ViewT>::isIndividualFrozen(AccountID const& account) const
// Can the specified account spend the specified currency issued by
// the specified issuer or does the freeze flag prohibit it?
template <typename ViewT>
bool
IOUIssuance<ViewT>::isFrozen(AccountID const& account, int /*depth*/) const
isFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer)
{
if (isXRP(currency_))
if (isXRP(currency))
return false;
if (this->isGlobalFrozen())
auto sle = view.read(keylet::account(issuer));
if (sle && sle->isFlag(lsfGlobalFreeze))
return true;
auto const& issuer = this->id();
if (issuer != account)
{
// Check if the issuer froze the line
auto const sleLine = this->readView().read(keylet::line(account, issuer, currency_));
if (sleLine && sleLine->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
sle = view.read(keylet::line(account, issuer, currency));
if (sle && sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
return true;
}
return false;
}
template <typename ViewT>
bool
IOUIssuance<ViewT>::isDeepFrozen(AccountID const& account) const
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer)
{
if (isXRP(currency_))
if (isXRP(currency))
{
return false;
}
auto const& issuer = this->id();
if (issuer == account)
{
return false;
}
auto const sle = this->readView().read(keylet::line(account, issuer, currency_));
auto const sle = view.read(keylet::line(account, issuer, currency));
if (!sle)
{
return false;
@@ -185,14 +177,14 @@ IOUIssuance<ViewT>::isDeepFrozen(AccountID const& account) const
//
//------------------------------------------------------------------------------
template <typename ViewT>
TER
IOUIssuance<ViewT>::trustCreate(
trustCreate(
ApplyView& view,
bool const bSrcHigh,
AccountID const& uSrcAccountID,
AccountID const& uDstAccountID,
uint256 const& uIndex, // ripple state entry
WAccountRoot& wrappedAcct, // the account being set.
SLE::ref sleAccount, // the account being set.
bool const bAuth, // authorize account.
bool const bNoRipple, // others cannot ripple through
bool const bFreeze, // funds cannot leave
@@ -204,9 +196,7 @@ IOUIssuance<ViewT>::trustCreate(
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
beast::Journal j)
requires kIsWritable
{
ApplyView& view = this->applyView();
JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
<< to_string(uDstAccountID) << ", " << saBalance.getFullText();
@@ -239,15 +229,15 @@ IOUIssuance<ViewT>::trustCreate(
bool const bSetDst = saLimit.getIssuer() == uDstAccountID;
bool const bSetHigh = bSrcHigh ^ bSetDst;
XRPL_ASSERT(wrappedAcct, "xrpl::trustCreate : non-null SLE");
if (!wrappedAcct)
XRPL_ASSERT(sleAccount, "xrpl::trustCreate : non-null SLE");
if (!sleAccount)
return tefINTERNAL; // LCOV_EXCL_LINE
XRPL_ASSERT(
wrappedAcct->getAccountID(sfAccount) == (bSetHigh ? uHighAccountID : uLowAccountID),
sleAccount->getAccountID(sfAccount) == (bSetHigh ? uHighAccountID : uLowAccountID),
"xrpl::trustCreate : matching account ID");
auto const peer = AccountRoot(bSetHigh ? uLowAccountID : uHighAccountID, view, j);
if (!peer.exists())
auto const slePeer = view.peek(keylet::account(bSetHigh ? uLowAccountID : uHighAccountID));
if (!slePeer)
return tecNO_TARGET;
// Remember deletion hints.
@@ -257,7 +247,7 @@ IOUIssuance<ViewT>::trustCreate(
sleRippleState->setFieldAmount(bSetHigh ? sfHighLimit : sfLowLimit, saLimit);
sleRippleState->setFieldAmount(
bSetHigh ? sfLowLimit : sfHighLimit,
STAmount(Issue{currency_, bSetDst ? uSrcAccountID : uDstAccountID}));
STAmount(Issue{saBalance.get<Issue>().currency, bSetDst ? uSrcAccountID : uDstAccountID}));
if (uQualityIn != 0u)
sleRippleState->setFieldU32(bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
@@ -284,14 +274,14 @@ IOUIssuance<ViewT>::trustCreate(
uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
}
if ((peer->getFlags() & lsfDefaultRipple) == 0)
if (!slePeer->isFlag(lsfDefaultRipple))
{
// The other side's default is no rippling
uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
}
sleRippleState->setFieldU32(sfFlags, uFlags);
wrappedAcct.adjustOwnerCount(1);
adjustOwnerCount(view, sleAccount, 1, j);
// ONLY: Create ripple balance.
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
@@ -352,8 +342,8 @@ updateTrustLine(
if (!state)
return false;
WAccountRoot wrappedAcct(sender, view, j);
if (!wrappedAcct)
auto sle = view.peek(keylet::account(sender));
if (!sle)
return false;
auto const senderReserveFlag = bSenderHigh ? lsfHighReserve : lsfLowReserve;
@@ -368,7 +358,7 @@ updateTrustLine(
// Sender is zero or negative.
&& state->isFlag(senderReserveFlag)
// Sender reserve is set.
&& state->isFlag(senderNoRippleFlag) != wrappedAcct->isFlag(lsfDefaultRipple) &&
&& state->isFlag(senderNoRippleFlag) != sle->isFlag(lsfDefaultRipple) &&
!state->isFlag(senderFreezeFlag) &&
!state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
// Sender trust limit is 0.
@@ -379,7 +369,7 @@ updateTrustLine(
{
// VFALCO Where is the line being deleted?
// Clear the reserve of the sender, possibly delete the line!
wrappedAcct.adjustOwnerCount(-1);
adjustOwnerCount(view, sle, -1, j);
// Clear reserve flag.
state->clearFlag(senderReserveFlag);
@@ -391,27 +381,29 @@ updateTrustLine(
return false;
}
template <typename ViewT>
TER
IOUIssuance<ViewT>::issue(AccountID const& account, STAmount const& amount, beast::Journal j)
requires kIsWritable
issueIOU(
ApplyView& view,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
beast::Journal j)
{
ApplyView& view = this->applyView();
auto const& issuerId = this->id();
XRPL_ASSERT(
!isXRP(account) && !isXRP(issuerId), "xrpl::issueIOU : neither account nor issuer is XRP");
!isXRP(account) && !isXRP(issue.account),
"xrpl::issueIOU : neither account nor issuer is XRP");
// Consistency check
XRPL_ASSERT(this->getIssue() == amount.get<Issue>(), "xrpl::issueIOU : matching issue");
XRPL_ASSERT(issue == amount.get<Issue>(), "xrpl::issueIOU : matching issue");
// Can't send to self!
XRPL_ASSERT(issuerId != account, "xrpl::issueIOU : not issuer account");
XRPL_ASSERT(issue.account != account, "xrpl::issueIOU : not issuer account");
JLOG(j.trace()) << "issueIOU: " << to_string(account) << ": " << amount.getFullText();
bool const bSenderHigh = issuerId > account;
bool const bSenderHigh = issue.account > account;
auto const index = keylet::line(issuerId, account, currency_);
auto const index = keylet::line(issue.account, account, issue.currency);
if (auto state = view.peek(index))
{
@@ -425,9 +417,9 @@ IOUIssuance<ViewT>::issue(AccountID const& account, STAmount const& amount, beas
finalBalance -= amount;
auto const mustDelete =
updateTrustLine(view, state, bSenderHigh, issuerId, startBalance, finalBalance, j);
updateTrustLine(view, state, bSenderHigh, issue.account, startBalance, finalBalance, j);
view.creditHookIOU(issuerId, account, amount, startBalance);
view.creditHookIOU(issue.account, account, amount, startBalance);
if (bSenderHigh)
finalBalance.negate();
@@ -439,7 +431,11 @@ IOUIssuance<ViewT>::issue(AccountID const& account, STAmount const& amount, beas
if (mustDelete)
{
return trustDelete(
view, state, bSenderHigh ? account : issuerId, bSenderHigh ? issuerId : account, j);
view,
state,
bSenderHigh ? account : issue.account,
bSenderHigh ? issue.account : account,
j);
}
view.update(state);
@@ -450,20 +446,21 @@ IOUIssuance<ViewT>::issue(AccountID const& account, STAmount const& amount, beas
// NIKB TODO: The limit uses the receiver's account as the issuer and
// this is unnecessarily inefficient as copying which could be avoided
// is now required. Consider available options.
STAmount const limit(Issue{currency_, account});
STAmount const limit(Issue{issue.currency, account});
STAmount finalBalance = amount;
finalBalance.get<Issue>().account = noAccount();
WAccountRoot receiverAccount(account, view, j);
auto const receiverAccount = view.peek(keylet::account(account));
if (!receiverAccount)
return tefINTERNAL; // LCOV_EXCL_LINE
bool const noRipple = !receiverAccount->isFlag(lsfDefaultRipple);
return trustCreate(
view,
bSenderHigh,
issuerId,
issue.account,
account,
index.key,
receiverAccount,
@@ -478,27 +475,29 @@ IOUIssuance<ViewT>::issue(AccountID const& account, STAmount const& amount, beas
j);
}
template <typename ViewT>
TER
IOUIssuance<ViewT>::redeem(AccountID const& account, STAmount const& amount, beast::Journal j)
requires kIsWritable
redeemIOU(
ApplyView& view,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
beast::Journal j)
{
ApplyView& view = this->applyView();
auto const& issuerId = this->id();
XRPL_ASSERT(
!isXRP(account) && !isXRP(issuerId), "xrpl::redeemIOU : neither account nor issuer is XRP");
!isXRP(account) && !isXRP(issue.account),
"xrpl::redeemIOU : neither account nor issuer is XRP");
// Consistency check
XRPL_ASSERT(this->getIssue() == amount.get<Issue>(), "xrpl::redeemIOU : matching issue");
XRPL_ASSERT(issue == amount.get<Issue>(), "xrpl::redeemIOU : matching issue");
// Can't send to self!
XRPL_ASSERT(issuerId != account, "xrpl::redeemIOU : not issuer account");
XRPL_ASSERT(issue.account != account, "xrpl::redeemIOU : not issuer account");
JLOG(j.trace()) << "redeemIOU: " << to_string(account) << ": " << amount.getFullText();
bool const bSenderHigh = account > issuerId;
bool const bSenderHigh = account > issue.account;
if (auto state = view.peek(keylet::line(account, issuerId, currency_)))
if (auto state = view.peek(keylet::line(account, issue.account, issue.currency)))
{
STAmount finalBalance = state->getFieldAmount(sfBalance);
@@ -512,7 +511,7 @@ IOUIssuance<ViewT>::redeem(AccountID const& account, STAmount const& amount, bea
auto const mustDelete =
updateTrustLine(view, state, bSenderHigh, account, startBalance, finalBalance, j);
view.creditHookIOU(account, issuerId, amount, startBalance);
view.creditHookIOU(account, issue.account, amount, startBalance);
if (bSenderHigh)
finalBalance.negate();
@@ -525,7 +524,11 @@ IOUIssuance<ViewT>::redeem(AccountID const& account, STAmount const& amount, bea
if (mustDelete)
{
return trustDelete(
view, state, bSenderHigh ? issuerId : account, bSenderHigh ? account : issuerId, j);
view,
state,
bSenderHigh ? issue.account : account,
bSenderHigh ? account : issue.account,
j);
}
view.update(state);
@@ -549,26 +552,25 @@ IOUIssuance<ViewT>::redeem(AccountID const& account, STAmount const& amount, bea
//
//------------------------------------------------------------------------------
template <typename ViewT>
TER
IOUIssuance<ViewT>::requireAuth(AccountID const& account, AuthType authType, int /*depth*/) const
requireAuth(ReadView const& view, Issue const& issue, AccountID const& account, AuthType authType)
{
auto const& issuer = this->id();
if (isXRP(currency_) || issuer == account)
if (isXRP(issue) || issue.account == account)
return tesSUCCESS;
auto const trustLine = this->readView().read(keylet::line(account, issuer, currency_));
auto const trustLine = view.read(keylet::line(account, issue.account, issue.currency));
// If account has no line, and this is a strong check, fail
if (!trustLine && authType == AuthType::StrongAuth)
return tecNO_LINE;
// If this is a weak or legacy check, or if the account has a line, fail if
// auth is required and not set on the line
if (this->exists() && (static_cast<unsigned int>(this->sle_->isFlag(lsfRequireAuth)) != 0u))
if (auto const issuerAccount = view.read(keylet::account(issue.account));
issuerAccount && issuerAccount->isFlag(lsfRequireAuth))
{
if (trustLine)
{
return trustLine->isFlag((account > issuer) ? lsfLowAuth : lsfHighAuth)
return trustLine->isFlag((account > issue.account) ? lsfLowAuth : lsfHighAuth)
? tesSUCCESS
: TER{tecNO_AUTH};
}
@@ -578,29 +580,29 @@ IOUIssuance<ViewT>::requireAuth(AccountID const& account, AuthType authType, int
return tesSUCCESS;
}
template <typename ViewT>
TER
IOUIssuance<ViewT>::canTransfer(AccountID const& from, AccountID const& to) const
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to)
{
if (isXRP(currency_))
if (issue.native())
return tesSUCCESS;
auto const& issuerId = this->id();
auto const& issuerId = issue.getIssuer();
if (issuerId == from || issuerId == to)
return tesSUCCESS;
if (!this->exists())
auto const sleIssuer = view.read(keylet::account(issuerId));
if (sleIssuer == nullptr)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const isRippleDisabled = [&](AccountID account) -> bool {
// Line might not exist, but some transfers can create it. If this
// is the case, just check the default ripple on the issuer account.
auto const line = this->readView().read(keylet::line(account, issuerId, currency_));
auto const line = view.read(keylet::line(account, issue));
if (line)
{
bool const issuerHigh = issuerId > account;
return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple);
}
return !this->sle_->isFlag(lsfDefaultRipple);
return !sleIssuer->isFlag(lsfDefaultRipple);
};
// Fail if rippling disabled on both trust lines
@@ -616,71 +618,85 @@ IOUIssuance<ViewT>::canTransfer(AccountID const& from, AccountID const& to) cons
//
//------------------------------------------------------------------------------
template <typename ViewT>
TER
IOUIssuance<ViewT>::addEmptyHolding(
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,
beast::Journal journal)
requires kIsWritable
{
ApplyView& view = this->applyView();
auto const& issuerId = this->id();
// The issuer can issue directly.
if (accountID == issuerId)
// Every account can hold XRP. An issuer can issue directly.
if (issue.native() || accountID == issue.getIssuer())
return tesSUCCESS;
if (this->isGlobalFrozen())
auto const& issuerId = issue.getIssuer();
auto const& currency = issue.currency;
if (isGlobalFrozen(view, issuerId))
return tecFROZEN; // LCOV_EXCL_LINE
auto const& srcId = issuerId;
auto const& dstId = accountID;
auto const high = srcId > dstId;
auto const index = keylet::line(srcId, dstId, currency_);
WAccountRoot wrappedSrc(srcId, view, journal);
WAccountRoot wrappedDst(dstId, view, journal);
if (!wrappedDst || !wrappedSrc)
auto const index = keylet::line(srcId, dstId, currency);
auto const sleSrc = view.peek(keylet::account(srcId));
auto const sleDst = view.peek(keylet::account(dstId));
if (!sleDst || !sleSrc)
return tefINTERNAL; // LCOV_EXCL_LINE
if (!wrappedSrc->isFlag(lsfDefaultRipple))
if (!sleSrc->isFlag(lsfDefaultRipple))
return tecINTERNAL; // LCOV_EXCL_LINE
// If the line already exists, don't create it again.
if (view.read(index))
return tecDUPLICATE;
// Can the account cover the trust line reserve ?
std::uint32_t const ownerCount = wrappedDst->at(sfOwnerCount);
std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
if (priorBalance < view.fees().accountReserve(ownerCount + 1))
return tecNO_LINE_INSUF_RESERVE;
return trustCreate(
view,
high,
srcId,
dstId,
index.key,
wrappedDst,
sleDst,
/*bAuth=*/false,
/*bNoRipple=*/true,
/*bFreeze=*/false,
/*deepFreeze*/ false,
/*saBalance=*/STAmount{Issue{currency_, noAccount()}},
/*saLimit=*/STAmount{Issue{currency_, dstId}},
/*saBalance=*/STAmount{Issue{currency, noAccount()}},
/*saLimit=*/STAmount{Issue{currency, dstId}},
/*uQualityIn=*/0,
/*uQualityOut=*/0,
journal);
}
template <typename ViewT>
TER
IOUIssuance<ViewT>::removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
requires kIsWritable
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
Issue const& issue,
beast::Journal journal)
{
ApplyView& view = this->applyView();
auto const& issuerId = this->id();
if (issue.native())
{
auto const sle = view.read(keylet::account(accountID));
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const balance = sle->getFieldAmount(sfBalance);
if (balance.xrp() != 0)
return tecHAS_OBLIGATIONS;
return tesSUCCESS;
}
// `asset` is an IOU.
// 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 == issuerId;
auto const line = view.peek(keylet::line(accountID, this->getIssue()));
bool const accountIsIssuer = accountID == issue.account;
auto const line = view.peek(keylet::line(accountID, issue));
if (!line)
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::kZero)
@@ -690,11 +706,11 @@ IOUIssuance<ViewT>::removeEmptyHolding(AccountID const& accountID, beast::Journa
if (line->isFlag(lsfLowReserve))
{
// Clear reserve for low account.
WAccountRoot wrappedLow(line->at(sfLowLimit)->getIssuer(), view, journal);
if (!wrappedLow)
auto sleLowAccount = view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
if (!sleLowAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
wrappedLow.adjustOwnerCount(-1);
adjustOwnerCount(view, sleLowAccount, -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.
@@ -704,11 +720,11 @@ IOUIssuance<ViewT>::removeEmptyHolding(AccountID const& accountID, beast::Journa
if (line->isFlag(lsfHighReserve))
{
// Clear reserve for high account.
WAccountRoot wrappedHigh(line->at(sfHighLimit)->getIssuer(), view, journal);
if (!wrappedHigh)
auto sleHighAccount = view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
if (!sleHighAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
wrappedHigh.adjustOwnerCount(-1);
adjustOwnerCount(view, sleHighAccount, -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.
@@ -732,13 +748,13 @@ deleteAMMTrustLine(
auto const& [low, high] = std::minmax(
sleState->getFieldAmount(sfLowLimit).getIssuer(),
sleState->getFieldAmount(sfHighLimit).getIssuer());
WAccountRoot wrappedLow(low, view, j);
WAccountRoot wrappedHigh(high, view, j);
if (!wrappedLow || !wrappedHigh)
auto sleLow = view.peek(keylet::account(low));
auto sleHigh = view.peek(keylet::account(high));
if (!sleLow || !sleHigh)
return tecINTERNAL; // LCOV_EXCL_LINE
bool const ammLow = wrappedLow->isFieldPresent(sfAMMID);
bool const ammHigh = wrappedHigh->isFieldPresent(sfAMMID);
bool const ammLow = sleLow->isFieldPresent(sfAMMID);
bool const ammHigh = sleHigh->isFieldPresent(sfAMMID);
// can't both be AMM
if (ammLow && ammHigh)
@@ -762,14 +778,25 @@ deleteAMMTrustLine(
if (!sleState->isFlag(uFlags))
return tecINTERNAL; // LCOV_EXCL_LINE
WAccountRoot wrappedHolder = !ammLow ? wrappedLow : wrappedHigh;
wrappedHolder.adjustOwnerCount(-1);
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
return tesSUCCESS;
}
// Explicit template instantiations
template class IOUIssuance<ReadView>;
template class IOUIssuance<ApplyView>;
TER
deleteAMMMPToken(
ApplyView& view,
std::shared_ptr<SLE> sleMpt,
AccountID const& ammAccountID,
beast::Journal j)
{
if (!view.dirRemove(
keylet::ownerDir(ammAccountID), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tefBAD_LEDGER; // LCOV_EXCL_LINE
view.erase(sleMpt);
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -51,28 +51,22 @@ bool
isGlobalFrozen(ReadView const& view, Asset const& asset)
{
return asset.visit(
[&](Issue const& issue) { return AccountRoot(issue.getIssuer(), view).isGlobalFrozen(); },
[&](MPTIssue const& issue) { return MPTokenIssuance(view, issue).isGlobalFrozen(); });
[&](Issue const& issue) { return isGlobalFrozen(view, issue.getIssuer()); },
[&](MPTIssue const& issue) { return isGlobalFrozen(view, issue); });
}
bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
{
return asset.visit(
[&](Issue const& issue) { return IOUIssuance(view, issue).isIndividualFrozen(account); },
[&](MPTIssue const& issue) {
return MPTokenIssuance(view, issue).isIndividualFrozen(account);
});
return std::visit(
[&](auto const& issue) { return isIndividualFrozen(view, account, issue); }, asset.value());
}
bool
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
{
return asset.visit(
[&](Issue const& issue) { return IOUIssuance(view, issue).isFrozen(account); },
[&](MPTIssue const& issue) {
return MPTokenIssuance(view, issue).isFrozen(account, depth);
});
return std::visit(
[&](auto const& issue) { return isFrozen(view, account, issue, depth); }, asset.value());
}
TER
@@ -100,10 +94,9 @@ isAnyFrozen(
std::initializer_list<AccountID> const& accounts,
Issue const& issue)
{
auto const iou = IOUIssuance(view, issue);
for (auto const& account : accounts)
{
if (iou.isFrozen(account))
if (isFrozen(view, account, issue.currency, issue.account))
return true;
}
return false;
@@ -118,9 +111,7 @@ isAnyFrozen(
{
return asset.visit(
[&](Issue const& issue) { return isAnyFrozen(view, accounts, issue); },
[&](MPTIssue const& issue) {
return MPTokenIssuance(view, issue).isAnyFrozen(accounts, depth);
});
[&](MPTIssue const& issue) { return isAnyFrozen(view, accounts, issue, depth); });
}
bool
@@ -128,7 +119,7 @@ isDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mpt
{
// Unlike IOUs, frozen / locked MPTs are not allowed to send or receive
// funds, so checking "deep frozen" is the same as checking "frozen".
return MPTokenIssuance(view, mptIssue).isFrozen(account, depth);
return isFrozen(view, account, mptIssue, depth);
}
bool
@@ -176,8 +167,8 @@ getLineIfUsable(
if (zeroIfFrozen == FreezeHandling::ZeroIfFrozen)
{
auto const iou = IOUIssuance(view, issuer, currency);
if (iou.isFrozen(account) || iou.isDeepFrozen(account))
if (isFrozen(view, account, currency, issuer) ||
isDeepFrozen(view, account, currency, issuer))
{
return nullptr;
}
@@ -186,14 +177,14 @@ getLineIfUsable(
// we need to check if the associated assets have been frozen
if (view.rules().enabled(fixFrozenLPTokenTransfer))
{
auto const issuerRoot = AccountRoot(issuer, view, j);
if (!issuerRoot.exists())
auto const sleIssuer = view.read(keylet::account(issuer));
if (!sleIssuer)
{
return nullptr; // LCOV_EXCL_LINE
}
if (issuerRoot->isFieldPresent(sfAMMID))
if (sleIssuer->isFieldPresent(sfAMMID))
{
auto const sleAmm = view.read(keylet::amm((*issuerRoot)[sfAMMID]));
auto const sleAmm = view.read(keylet::amm((*sleIssuer)[sfAMMID]));
if (!sleAmm ||
isLPTokenFrozen(view, account, (*sleAmm)[sfAsset], (*sleAmm)[sfAsset2]))
@@ -258,8 +249,7 @@ accountHolds(
STAmount const amount;
if (isXRP(currency))
{
AccountRoot const accountRoot(account, view, j);
return {accountRoot.xrpLiquid(0)};
return {xrpLiquid(view, account, 0, j)};
}
bool const returnSpendable = (includeFullBalance == SpendableHandling::FullBalance);
@@ -327,9 +317,7 @@ accountHolds(
{
amount.clear(mptIssue);
}
else if (
zeroIfFrozen == FreezeHandling::ZeroIfFrozen &&
MPTokenIssuance(view, mptIssue).isFrozen(account))
else if (zeroIfFrozen == FreezeHandling::ZeroIfFrozen && isFrozen(view, account, mptIssue))
{
amount.clear(mptIssue);
}
@@ -342,8 +330,7 @@ accountHolds(
if (zeroIfUnauthorized == AuthHandling::ZeroIfUnauthorized &&
view.rules().enabled(featureSingleAssetVault))
{
if (auto const err =
MPTokenIssuance(view, mptIssue).requireAuth(account, AuthType::StrongAuth);
if (auto const err = requireAuth(view, mptIssue, account, AuthType::StrongAuth);
!isTesSuccess(err))
amount.clear(mptIssue);
}
@@ -428,8 +415,8 @@ Rate
transferRate(ReadView const& view, STAmount const& amount)
{
return amount.asset().visit(
[&](Issue const& issue) { return IOUIssuance(view, issue).transferRate(); },
[&](MPTIssue const& issue) { return MPTokenIssuance(view, issue).transferRate(); });
[&](Issue const& issue) { return transferRate(view, issue.getIssuer()); },
[&](MPTIssue const& issue) { return transferRate(view, issue.getMptID()); });
}
//------------------------------------------------------------------------------
@@ -445,13 +432,12 @@ canAddHolding(ReadView const& view, Issue const& issue)
{
return tesSUCCESS; // No special checks for XRP
}
auto const issuer = AccountRoot(issue.getIssuer(), view);
if (!issuer.exists())
auto const issuer = view.read(keylet::account(issue.getIssuer()));
if (!issuer)
{
return terNO_ACCOUNT;
}
if (!issuer->isFlag(lsfDefaultRipple))
{
return terNO_RIPPLE;
@@ -476,18 +462,11 @@ addEmptyHolding(
Asset const& asset,
beast::Journal journal)
{
// Every account can hold XRP.
if (asset.native())
return tesSUCCESS;
return asset.visit(
[&](Issue const& issue) {
return WIOUIssuance(view, issue, journal)
.addEmptyHolding(accountID, priorBalance, journal);
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return addEmptyHolding(view, accountID, priorBalance, issue, journal);
},
[&](MPTIssue const& issue) {
return WMPTokenIssuance(view, issue, journal)
.addEmptyHolding(accountID, priorBalance, journal);
});
asset.value());
}
TER
@@ -497,25 +476,11 @@ removeEmptyHolding(
Asset const& asset,
beast::Journal journal)
{
if (asset.native())
{
auto const account = AccountRoot(accountID, view, journal);
if (!account.exists())
return tecINTERNAL; // LCOV_EXCL_LINE
auto const balance = account->getFieldAmount(sfBalance);
if (balance.xrp() != 0)
return tecHAS_OBLIGATIONS;
return tesSUCCESS;
}
return asset.visit(
[&](Issue const& issue) {
return WIOUIssuance(view, issue, journal).removeEmptyHolding(accountID, journal);
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return removeEmptyHolding(view, accountID, issue, journal);
},
[&](MPTIssue const& issue) {
return WMPTokenIssuance(view, issue, journal).removeEmptyHolding(accountID, journal);
});
asset.value());
}
//------------------------------------------------------------------------------
@@ -527,19 +492,21 @@ removeEmptyHolding(
TER
requireAuth(ReadView const& view, Asset const& asset, AccountID const& account, AuthType authType)
{
return asset.visit(
[&](Issue const& issue) { return IOUIssuance(view, issue).requireAuth(account, authType); },
[&](MPTIssue const& issue) {
return MPTokenIssuance(view, issue).requireAuth(account, authType);
});
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
return requireAuth(view, issue, account, authType);
},
asset.value());
}
TER
canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to)
{
return asset.visit(
[&](Issue const& issue) { return IOUIssuance(view, issue).canTransfer(from, to); },
[&](MPTIssue const& issue) { return MPTokenIssuance(view, issue).canTransfer(from, to); });
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return canTransfer(view, issue, from, to);
},
asset.value());
}
//------------------------------------------------------------------------------
@@ -551,7 +518,7 @@ canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, Acc
// Direct send w/o fees:
// - Redeeming IOUs and/or sending sender's own IOUs.
// - Create trust line if needed.
// bCheckIssuer : normally require issuer to be involved.
// --> bCheckIssuer : normally require issuer to be involved.
static TER
directSendNoFeeIOU(
ApplyView& view,
@@ -618,7 +585,7 @@ directSendNoFeeIOU(
&& sleRippleState->isFlag(senderReserveFlag)
// Sender reserve is set.
&& sleRippleState->isFlag(senderNoRippleFlag) !=
AccountRoot(uSenderID, view, j)->isFlag(lsfDefaultRipple) &&
view.read(keylet::account(uSenderID))->isFlag(lsfDefaultRipple) &&
!sleRippleState->isFlag(senderFreezeFlag) &&
!sleRippleState->getFieldAmount(bSenderHigh ? sfHighLimit : sfLowLimit)
// Sender trust limit is 0.
@@ -629,8 +596,7 @@ directSendNoFeeIOU(
// Sender quality out is 0.
{
// Clear the reserve of the sender, possibly delete the line!
WAccountRoot wrappedSender(uSenderID, view, j);
wrappedSender.adjustOwnerCount(-1);
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), -1, j);
// Clear reserve flag.
sleRippleState->clearFlag(senderReserveFlag);
@@ -672,28 +638,28 @@ directSendNoFeeIOU(
<< to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : "
<< saAmount.getFullText();
WAccountRoot wrappedAccount(uReceiverID, view, j);
if (!wrappedAccount)
auto const sleAccount = view.peek(keylet::account(uReceiverID));
if (!sleAccount)
return tefINTERNAL; // LCOV_EXCL_LINE
bool const noRipple = !wrappedAccount->isFlag(lsfDefaultRipple);
bool const noRipple = !sleAccount->isFlag(lsfDefaultRipple);
return WIOUIssuance(view, saAmount.get<Issue>(), j)
.trustCreate(
bSenderHigh,
uSenderID,
uReceiverID,
index.key,
wrappedAccount,
false,
noRipple,
false,
false,
saBalance,
saReceiverLimit,
0,
0,
j);
return trustCreate(
view,
bSenderHigh,
uSenderID,
uReceiverID,
index.key,
sleAccount,
false,
noRipple,
false,
false,
saBalance,
saReceiverLimit,
0,
0,
j);
}
// Send regardless of limits.
@@ -730,10 +696,8 @@ directSendNoLimitIOU(
// Calculate the amount to transfer accounting
// for any transfer fees if the fee is not waived:
WAccountRoot const wrappedIssuer(issuer, view, j);
saActual = (waiveFee == WaiveTransferFee::Yes)
? saAmount
: multiply(saAmount, wrappedIssuer.transferRate());
saActual = (waiveFee == WaiveTransferFee::Yes) ? saAmount
: multiply(saAmount, transferRate(view, issuer));
JLOG(j.debug()) << "directSendNoLimitIOU> " << to_string(uSenderID) << " - > "
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
@@ -798,10 +762,9 @@ directSendNoLimitMultiIOU(
// Calculate the amount to transfer accounting
// for any transfer fees if the fee is not waived:
WAccountRoot const wrappedIssuer(issuer, view, j);
STAmount const actualSend = (waiveFee == WaiveTransferFee::Yes)
? amount
: multiply(amount, wrappedIssuer.transferRate());
: multiply(amount, transferRate(view, issuer));
actual += actualSend;
takeFromSender += actualSend;
@@ -871,8 +834,10 @@ accountSendIOU(
*/
TER terResult(tesSUCCESS);
WAccountRoot sender(uSenderID, view, j);
WAccountRoot receiver(uReceiverID, view, j);
SLE::pointer const sender =
uSenderID != beast::kZero ? view.peek(keylet::account(uSenderID)) : SLE::pointer();
SLE::pointer const receiver =
uReceiverID != beast::kZero ? view.peek(keylet::account(uReceiverID)) : SLE::pointer();
if (auto stream = j.trace())
{
@@ -906,7 +871,7 @@ accountSendIOU(
// Decrement XRP balance.
sender->setFieldAmount(sfBalance, sndBal - saAmount);
sender.update();
view.update(sender);
}
}
@@ -917,7 +882,7 @@ accountSendIOU(
receiver->setFieldAmount(sfBalance, rcvBal + saAmount);
view.creditHookIOU(xrpAccount(), uReceiverID, saAmount, -rcvBal);
receiver.update();
view.update(receiver);
}
if (auto stream = j.trace())
@@ -965,7 +930,8 @@ accountSendMultiIOU(
* ensure that transfers are balanced.
*/
WAccountRoot sender(senderID, view, j);
SLE::pointer const sender =
senderID != beast::kZero ? view.peek(keylet::account(senderID)) : SLE::pointer();
if (auto stream = j.trace())
{
@@ -996,7 +962,8 @@ accountSendMultiIOU(
if (!amount || (senderID == receiverID))
continue;
WAccountRoot receiver(receiverID, view, j);
SLE::pointer const receiver =
receiverID != beast::kZero ? view.peek(keylet::account(receiverID)) : SLE::pointer();
if (auto stream = j.trace())
{
@@ -1017,7 +984,7 @@ accountSendMultiIOU(
receiver->setFieldAmount(sfBalance, rcvBal + amount);
view.creditHookIOU(xrpAccount(), receiverID, amount, -rcvBal);
receiver.update();
view.update(receiver);
// Take what is actually sent
takeFromSender += amount;
@@ -1047,7 +1014,7 @@ accountSendMultiIOU(
// Decrement XRP balance.
sender->setFieldAmount(sfBalance, sndBal - takeFromSender);
sender.update();
view.update(sender);
}
if (auto stream = j.trace())
@@ -1189,7 +1156,7 @@ directSendNoLimitMPT(
// Sending 3rd party MPTs: transit.
saActual = (waiveFee == WaiveTransferFee::Yes)
? saAmount
: multiply(saAmount, MPTokenIssuance(view, saAmount.get<MPTIssue>()).transferRate());
: multiply(saAmount, transferRate(view, saAmount.get<MPTIssue>().getMptID()));
JLOG(j.debug()) << "directSendNoLimitMPT> " << to_string(uSenderID) << " - > "
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
@@ -1300,7 +1267,7 @@ directSendNoLimitMultiMPT(
// Sending 3rd party MPTs: transit.
STAmount const actualSend = (waiveFee == WaiveTransferFee::Yes)
? amount
: multiply(amount, MPTokenIssuance(view, amount.get<MPTIssue>()).transferRate());
: multiply(amount, transferRate(view, amount.get<MPTIssue>().getMptID()));
actual += actualSend;
takeFromSender += actualSend;
@@ -1440,15 +1407,15 @@ transferXRP(
XRPL_ASSERT(from != to, "xrpl::transferXRP : sender is not receiver");
XRPL_ASSERT(amount.native(), "xrpl::transferXRP : amount is XRP");
WAccountRoot acctSender(from, view, j);
WAccountRoot acctReceiver(to, view, j);
if (!acctSender || !acctReceiver)
SLE::pointer const sender = view.peek(keylet::account(from));
SLE::pointer const receiver = view.peek(keylet::account(to));
if (!sender || !receiver)
return tefINTERNAL; // LCOV_EXCL_LINE
JLOG(j.trace()) << "transferXRP: " << to_string(from) << " -> " << to_string(to)
<< ") : " << amount.getFullText();
if (acctSender->getFieldAmount(sfBalance) < amount)
if (sender->getFieldAmount(sfBalance) < amount)
{
// VFALCO Its unfortunate we have to keep
// mutating these TER everywhere
@@ -1459,11 +1426,11 @@ transferXRP(
}
// Decrement XRP balance.
acctSender->setFieldAmount(sfBalance, acctSender->getFieldAmount(sfBalance) - amount);
acctSender.update();
sender->setFieldAmount(sfBalance, sender->getFieldAmount(sfBalance) - amount);
view.update(sender);
acctReceiver->setFieldAmount(sfBalance, acctReceiver->getFieldAmount(sfBalance) + amount);
acctReceiver.update();
receiver->setFieldAmount(sfBalance, receiver->getFieldAmount(sfBalance) + amount);
view.update(receiver);
return tesSUCCESS;
}

View File

@@ -518,7 +518,7 @@ oracle(AccountID const& account, std::uint32_t const& documentID) noexcept
}
Keylet
mptIssuance(std::uint32_t const seq, AccountID const& issuer) noexcept
mptIssuance(std::uint32_t seq, AccountID const& issuer) noexcept
{
return mptIssuance(makeMptID(seq, issuer));
}

View File

@@ -106,6 +106,7 @@ transResults()
MAKE_ERROR(tecLIMIT_EXCEEDED, "Limit exceeded."),
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(tecOUT_OF_GAS, "The WASM code ran out of gas during execution."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
@@ -199,6 +200,7 @@ transResults()
MAKE_ERROR(temARRAY_TOO_LARGE, "Malformed: Array is too large."),
MAKE_ERROR(temBAD_TRANSFER_FEE, "Malformed: Transfer fee is outside valid range."),
MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."),
MAKE_ERROR(temBAD_WASM, "Malformed: Provided WASM code is invalid."),
MAKE_ERROR(terRETRY, "Retry transaction."),
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),

View File

@@ -15,7 +15,6 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DelegateHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
@@ -264,7 +263,6 @@ Transactor::Transactor(ApplyContext& ctx)
, sink_(ctx.journal, toShortString(ctx.tx.getTransactionID()) + " ")
, j_(sink_)
, accountID_(ctx.tx.getAccountID(sfAccount))
, account_(accountID_, ctx.view())
{
}
@@ -390,11 +388,11 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
return tesSUCCESS;
auto const id = ctx.tx.getFeePayer();
AccountRoot const acctRoot(id, ctx.view);
if (!acctRoot)
auto const sle = ctx.view.read(keylet::account(id));
if (!sle)
return terNO_ACCOUNT;
auto const balance = (*acctRoot)[sfBalance].xrp();
auto const balance = (*sle)[sfBalance].xrp();
// NOTE: Because preclaim evaluates against a static readview, it
// does not reflect fee deductions from other transactions paid by
@@ -426,15 +424,17 @@ Transactor::payFee()
auto const feePaid = ctx_.tx[sfFee].xrp();
auto const feePayer = ctx_.tx.getFeePayer();
WAccountRoot payerAcct(feePayer, view(), j_);
if (!payerAcct)
auto const sle = view().peek(keylet::account(feePayer));
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.
payerAcct->setFieldAmount(sfBalance, payerAcct->getFieldAmount(sfBalance) - feePaid);
sle->setFieldAmount(sfBalance, sle->getFieldAmount(sfBalance) - feePaid);
if (feePayer != accountID_)
payerAcct.update(); // done in `apply()` for the account
view().update(sle); // done in `apply()` for the account
// VFALCO Should we call view().rawDestroyXRP() here as well?
return tesSUCCESS;
}
@@ -443,9 +443,9 @@ Transactor::checkSeqProxy(ReadView const& view, STTx const& tx, beast::Journal j
{
auto const id = tx.getAccountID(sfAccount);
AccountRoot const acctRoot(id, view);
auto const sle = view.read(keylet::account(id));
if (!acctRoot)
if (!sle)
{
JLOG(j.trace()) << "applyTransaction: delay: source account does not exist "
<< toBase58(id);
@@ -453,7 +453,7 @@ Transactor::checkSeqProxy(ReadView const& view, STTx const& tx, beast::Journal j
}
SeqProxy const tSeqProx = tx.getSeqProxy();
SeqProxy const aSeq = SeqProxy::sequence((*acctRoot)[sfSequence]);
SeqProxy const aSeq = SeqProxy::sequence((*sle)[sfSequence]);
if (tSeqProx.isSeq())
{
@@ -508,9 +508,9 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx)
{
auto const id = ctx.tx.getAccountID(sfAccount);
AccountRoot const acctRoot(id, ctx.view);
auto const sle = ctx.view.read(keylet::account(id));
if (!acctRoot)
if (!sle)
{
JLOG(ctx.j.trace()) << "applyTransaction: delay: source account does not exist "
<< toBase58(id);
@@ -518,7 +518,7 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx)
}
if (ctx.tx.isFieldPresent(sfAccountTxnID) &&
(acctRoot->getFieldH256(sfAccountTxnID) != ctx.tx.getFieldH256(sfAccountTxnID)))
(sle->getFieldH256(sfAccountTxnID) != ctx.tx.getFieldH256(sfAccountTxnID)))
return tefWRONG_PRIOR;
if (ctx.tx.isFieldPresent(sfLastLedgerSequence) &&
@@ -577,8 +577,8 @@ Transactor::ticketDelete(
// Update the account root's TicketCount. If the ticket count drops to
// zero remove the (optional) field.
WAccountRoot wrappedAcct(account, view, j);
if (!wrappedAcct)
auto sleAccount = view.peek(keylet::account(account));
if (!sleAccount)
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Could not find Ticket owner account root.";
@@ -586,11 +586,11 @@ Transactor::ticketDelete(
// LCOV_EXCL_STOP
}
if (auto ticketCount = (*wrappedAcct)[~sfTicketCount])
if (auto ticketCount = (*sleAccount)[~sfTicketCount])
{
if (*ticketCount == 1)
{
wrappedAcct->makeFieldAbsent(sfTicketCount);
sleAccount->makeFieldAbsent(sfTicketCount);
}
else
{
@@ -606,7 +606,7 @@ Transactor::ticketDelete(
}
// Update the Ticket owner's reserve.
wrappedAcct.adjustOwnerCount(-1);
adjustOwnerCount(view, sleAccount, -1, j);
// Remove Ticket from ledger.
view.erase(sleTicket);
@@ -627,19 +627,19 @@ Transactor::apply()
// If the transactor requires a valid account and the transaction doesn't
// list one, preflight will have already a flagged a failure.
WAccountRoot acct(accountID_, view(), j_);
auto const sle = view().peek(keylet::account(accountID_));
// acct must exist except for transactions
// sle must exist except for transactions
// that allow zero account.
XRPL_ASSERT(
acct.exists() || accountID_ == beast::kZero,
sle != nullptr || accountID_ == beast::kZero,
"xrpl::Transactor::apply : non-null SLE or zero account");
if (acct)
if (sle)
{
preFeeBalance_ = STAmount{(*acct)[sfBalance]}.xrp();
preFeeBalance_ = STAmount{(*sle)[sfBalance]}.xrp();
TER result = consumeSeqProxy(acct.mutableSle());
TER result = consumeSeqProxy(sle);
if (!isTesSuccess(result))
return result;
@@ -647,10 +647,10 @@ Transactor::apply()
if (!isTesSuccess(result))
return result;
if (acct->isFieldPresent(sfAccountTxnID))
acct->setFieldH256(sfAccountTxnID, ctx_.tx.getTransactionID());
if (sle->isFieldPresent(sfAccountTxnID))
sle->setFieldH256(sfAccountTxnID, ctx_.tx.getTransactionID());
acct.update();
view().update(sle);
}
return doApply();
@@ -665,9 +665,10 @@ Transactor::checkSign(
STObject const& sigObject,
beast::Journal const j)
{
AccountRoot const acctSign(idAccount, view);
{
if (view.rules().enabled(featureLendingProtocol) && acctSign.isPseudoAccount())
auto const sle = view.read(keylet::account(idAccount));
if (view.rules().enabled(featureLendingProtocol) && isPseudoAccount(sle))
{
// Pseudo-accounts can't sign transactions. This check is gated on
// the Lending Protocol amendment because that's the project it was
@@ -713,11 +714,12 @@ Transactor::checkSign(
}
// Look up the account.
if (!acctSign)
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
auto const sleAccount = view.read(keylet::account(idAccount));
if (!sleAccount)
return terNO_ACCOUNT;
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
return checkSingleSign(view, idSigner, idAccount, acctSign.sle(), j);
return checkSingleSign(view, idSigner, idAccount, sleAccount, j);
}
NotTEC
@@ -752,11 +754,11 @@ Transactor::checkBatchSign(PreclaimContext const& ctx)
// LCOV_EXCL_STOP
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
AccountRoot const acctRoot(idAccount, ctx.view);
auto const sleAccount = ctx.view.read(keylet::account(idAccount));
// A batch can include transactions from an un-created account ONLY
// when the account master key is the signer
if (!acctRoot)
if (!sleAccount)
{
if (idAccount != idSigner)
return tefBAD_AUTH;
@@ -764,7 +766,7 @@ Transactor::checkBatchSign(PreclaimContext const& ctx)
return tesSUCCESS;
}
if (ret = checkSingleSign(ctx.view, idSigner, idAccount, acctRoot.sle(), ctx.j);
if (ret = checkSingleSign(ctx.view, idSigner, idAccount, sleAccount, ctx.j);
!isTesSuccess(ret))
return ret;
}
@@ -910,15 +912,15 @@ Transactor::checkMultiSign(
// In any of these cases we need to know whether the account is in
// the ledger. Determine that now.
AccountRoot const acctSigner(txSignerAcctID, view);
auto const sleTxSignerRoot = view.read(keylet::account(txSignerAcctID));
if (signingAcctIDFromPubKey == txSignerAcctID)
{
// Either Phantom or Master. Phantoms automatically pass.
if (acctSigner)
if (sleTxSignerRoot)
{
// Master Key. Account may not have asfDisableMaster set.
std::uint32_t const signerAccountFlags = acctSigner->getFieldU32(sfFlags);
std::uint32_t const signerAccountFlags = sleTxSignerRoot->getFieldU32(sfFlags);
if ((signerAccountFlags & lsfDisableMaster) != 0u)
{
@@ -931,19 +933,19 @@ Transactor::checkMultiSign(
{
// May be a Regular Key. Let's find out.
// Public key must hash to the account's regular key.
if (!acctSigner)
if (!sleTxSignerRoot)
{
JLOG(j.trace()) << "applyTransaction: Non-phantom signer "
"lacks account root.";
return tefBAD_SIGNATURE;
}
if (!acctSigner->isFieldPresent(sfRegularKey))
if (!sleTxSignerRoot->isFieldPresent(sfRegularKey))
{
JLOG(j.trace()) << "applyTransaction: Account lacks RegularKey.";
return tefBAD_SIGNATURE;
}
if (signingAcctIDFromPubKey != acctSigner->getAccountID(sfRegularKey))
if (signingAcctIDFromPubKey != sleTxSignerRoot->getAccountID(sfRegularKey))
{
JLOG(j.trace()) << "applyTransaction: Account doesn't match RegularKey.";
return tefBAD_SIGNATURE;
@@ -1072,25 +1074,18 @@ Transactor::reset(XRPAmount fee)
{
ctx_.discard();
WAccountRoot txnAcct(ctx_.tx.getAccountID(sfAccount), view(), j_);
auto const txnAcct = view().peek(keylet::account(ctx_.tx.getAccountID(sfAccount)));
// The account should never be missing from the ledger. But if it
// is missing then we can't very well charge it a fee, can we?
if (!txnAcct)
return {tefINTERNAL, beast::kZero};
auto const feePayer = ctx_.tx.getFeePayer();
bool const hasDelegateAcct = (feePayer != accountID_);
std::optional<WAccountRoot> delegateAcct;
if (hasDelegateAcct)
{
delegateAcct.emplace(feePayer, view(), j_);
if (!*delegateAcct)
return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
}
auto const payerSle = view().peek(keylet::account(ctx_.tx.getFeePayer()));
if (!payerSle)
return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
auto& payer = hasDelegateAcct ? *delegateAcct : txnAcct;
auto const balance = payer->getFieldAmount(sfBalance).xrp();
auto const balance = payerSle->getFieldAmount(sfBalance).xrp();
// balance should have already been checked in checkFee / preFlight.
XRPL_ASSERT(
@@ -1109,15 +1104,15 @@ 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.
payer->setFieldAmount(sfBalance, balance - fee);
TER const ter{consumeSeqProxy(txnAcct.mutableSle())};
payerSle->setFieldAmount(sfBalance, balance - fee);
TER const ter{consumeSeqProxy(txnAcct)};
XRPL_ASSERT(isTesSuccess(ter), "xrpl::Transactor::reset : result is tesSUCCESS");
if (isTesSuccess(ter))
{
txnAcct.update();
if (hasDelegateAcct)
delegateAcct->update();
view().update(txnAcct);
if (payerSle != txnAcct)
view().update(payerSle);
}
return {ter, fee};

View File

@@ -711,19 +711,6 @@ NoDeepFreezeTrustLinesWithoutFreeze::finalize(
//------------------------------------------------------------------------------
[[nodiscard]] static bool
isPseudoAccount(std::shared_ptr<SLE const> sleAcct)
{
auto const& fields = getPseudoAccountFields();
// Intentionally use defensive coding here because it's cheap and makes the
// semantics of true return value clean.
return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT &&
std::count_if(fields.begin(), fields.end(), [&sleAcct](SField const* sf) -> bool {
return sleAcct->isFieldPresent(*sf);
}) > 0;
}
void
ValidNewAccountRoot::visitEntry(
bool,

View File

@@ -3,7 +3,6 @@
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -107,11 +106,11 @@ ValidLoanBroker::finalize(
{
for (auto const& field : {&sfLowLimit, &sfHighLimit})
{
auto const account = AccountRoot(line->at(*field).getIssuer(), view, j);
auto const account = view.read(keylet::account(line->at(*field).getIssuer()));
// This Invariant doesn't know about the rules for Trust Lines, so
// if the account is missing, don't treat it as an error. This
// loop is only concerned with finding Broker pseudo-accounts
if (account.exists() && account->isFieldPresent(sfLoanBrokerID))
if (account && account->isFieldPresent(sfLoanBrokerID))
{
auto const& loanBrokerID = account->at(sfLoanBrokerID);
// create an entry if one doesn't already exist
@@ -121,11 +120,11 @@ ValidLoanBroker::finalize(
}
for (auto const& mpt : mpts_)
{
auto const account = AccountRoot(mpt->at(sfAccount), view, j);
auto const account = view.read(keylet::account(mpt->at(sfAccount)));
// This Invariant doesn't know about the rules for MPTokens, so
// if the account is missing, don't treat is as an error. This
// loop is only concerned with finding Broker pseudo-accounts
if (account.exists() && account->isFieldPresent(sfLoanBrokerID))
if (account && account->isFieldPresent(sfLoanBrokerID))
{
auto const& loanBrokerID = account->at(sfLoanBrokerID);
// create an entry if one doesn't already exist

View File

@@ -535,15 +535,16 @@ ValidVault::finalize(
result = false;
}
AccountRoot const acctSharesIssuer(updatedShares->share.getIssuer(), view);
if (!acctSharesIssuer)
auto const sleSharesIssuer =
view.read(keylet::account(updatedShares->share.getIssuer()));
if (!sleSharesIssuer)
{
JLOG(j.fatal()) //
<< "Invariant failed: shares issuer must exist";
return false;
}
if (!acctSharesIssuer.isPseudoAccount())
if (!isPseudoAccount(sleSharesIssuer))
{
JLOG(j.fatal()) //
<< "Invariant failed: shares issuer must be a "
@@ -551,7 +552,7 @@ ValidVault::finalize(
result = false;
}
if (auto const vaultId = acctSharesIssuer->at(~sfVaultID);
if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
!vaultId || *vaultId != afterVault.key)
{
JLOG(j.fatal()) //

View File

@@ -731,8 +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 =
WMPTokenIssuance(sb, assetIn.get<MPTIssue>(), j_).checkCreateMPT(owner, j_);
if (auto const err = checkCreateMPT(sb, assetIn.get<MPTIssue>(), owner, j_);
!isTesSuccess(err))
{
return true;
@@ -893,8 +892,7 @@ BookStep<TIn, TOut, TDerived>::consumeOffer(
if constexpr (std::is_same_v<TOut, MPTAmount>)
{
if (offer.owner() == issuer)
WMPTokenIssuance(sb, book_.out.get<MPTIssue>())
.issuerSelfDebitHook(ofrAmt.out.value());
issuerSelfDebitHookMPT(sb, book_.out.get<MPTIssue>(), ofrAmt.out.value());
}
}
@@ -1338,7 +1336,7 @@ BookStep<TIn, TOut, TDerived>::check(StrandContext const& ctx) const
}
auto issuerExists = [](ReadView const& view, Asset const& iss) -> bool {
return isXRP(iss.getIssuer()) || AccountRoot(iss.getIssuer(), view).exists();
return isXRP(iss.getIssuer()) || view.exists(keylet::account(iss.getIssuer()));
};
if (!issuerExists(ctx.view, book_.in) || !issuerExists(ctx.view, book_.out))
@@ -1390,8 +1388,8 @@ BookStep<TIn, TOut, TDerived>::rate(
if (isXRP(issuer) || issuer == dstAccount)
return kParityRate;
return asset.visit(
[&](Issue const&) { return AccountRoot(issuer, view).transferRate(); },
[&](MPTIssue const& issue) { return MPTokenIssuance(view, issue).transferRate(); });
[&](Issue const&) { return transferRate(view, issuer); },
[&](MPTIssue const& issue) { return transferRate(view, issue.getMptID()); });
};
template <class TIn, class TOut, class TDerived>
@@ -1415,7 +1413,7 @@ BookStep<TIn, TOut, TDerived>::checkMPTDEX(ReadView const& view, AccountID const
// BookStep. Fail both if in asset is locked. In the former case it is holder
// to locked holder transfer. In the latter case it is not possible to tell if
// it is issuer to holder or holder to holder transfer.
if (MPTokenIssuance(view, book_.in.get<MPTIssue>()).isFrozen(owner))
if (isFrozen(view, owner, book_.in.get<MPTIssue>()))
return false;
// Previous step is BookStep. BookStep only sends if CanTransfer is
// set and not locked or the offer is owned by an issuer

View File

@@ -448,11 +448,10 @@ DirectIPaymentStep::check(StrandContext const& ctx, std::shared_ptr<const SLE> c
}
{
auto const iou = IOUIssuance(ctx.view, src_, currency_);
auto const owed = iou.creditBalance(dst_);
auto const owed = creditBalance(ctx.view, dst_, src_, currency_);
if (owed <= beast::kZero)
{
auto const limit = iou.creditLimit(dst_);
auto const limit = creditLimit(ctx.view, dst_, src_, currency_);
if (-owed >= limit)
{
JLOG(j_.debug()) << "DirectStepI: dry: owed: " << owed << " limit: " << limit;
@@ -485,7 +484,7 @@ DirectStepI<TDerived>::maxPaymentFlow(ReadView const& sb) const
return {srcOwed, DebtDirection::Redeems};
// srcOwed is negative or zero
return {IOUIssuance(sb, src_, currency_).creditLimit2(dst_) + srcOwed, DebtDirection::Issues};
return {creditLimit2(sb, dst_, src_, currency_) + srcOwed, DebtDirection::Issues};
}
template <class TDerived>
@@ -762,10 +761,8 @@ DirectStepI<TDerived>::qualitiesSrcIssues(ReadView const& sb, DebtDirection prev
"xrpl::DirectStepI::qualitiesSrcIssues : will prevStepDebtDirection "
"issue");
AccountRoot const issuer(src_, sb);
std::uint32_t const srcQOut =
redeems(prevStepDebtDirection) ? issuer.transferRate().value : QUALITY_ONE;
redeems(prevStepDebtDirection) ? transferRate(sb, src_).value : QUALITY_ONE;
auto dstQIn = static_cast<TDerived const*>(this)->quality(sb, QualityDirection::In);
if (isLast_ && dstQIn > QUALITY_ONE)
@@ -838,8 +835,8 @@ DirectStepI<TDerived>::check(StrandContext const& ctx) const
return temBAD_PATH;
}
AccountRoot const acctSrc(src_, ctx.view);
if (!acctSrc)
auto const sleSrc = ctx.view.read(keylet::account(src_));
if (!sleSrc)
{
JLOG(j_.warn()) << "DirectStepI: can't receive IOUs from non-existent issuer: " << src_;
return terNO_ACCOUNT;
@@ -898,7 +895,7 @@ DirectStepI<TDerived>::check(StrandContext const& ctx) const
}
}
return static_cast<TDerived const*>(this)->check(ctx, acctSrc.sle());
return static_cast<TDerived const*>(this)->check(ctx, sleSrc);
}
//------------------------------------------------------------------------------

View File

@@ -4,8 +4,8 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -264,7 +264,7 @@ public:
// Verify the consistency of the step. These checks are specific to
// payments and assume that general checks were already performed.
[[nodiscard]] TER
check(StrandContext const& ctx, AccountRoot<ReadView> const& srcRoot) const;
check(StrandContext const& ctx, std::shared_ptr<const SLE> const& sleSrc) const;
[[nodiscard]] std::string
logString() const override
@@ -312,7 +312,7 @@ public:
// Verify the consistency of the step. These checks are specific to
// offer crossing and assume that general checks were already performed.
static TER
check(StrandContext const& ctx, AccountRoot<ReadView> const& srcRoot);
check(StrandContext const& ctx, std::shared_ptr<const SLE> const& sleSrc);
[[nodiscard]] std::string
logString() const override
@@ -328,7 +328,8 @@ public:
//------------------------------------------------------------------------------
TER
MPTEndpointPaymentStep::check(StrandContext const& ctx, AccountRoot<ReadView> const& srcRoot) const
MPTEndpointPaymentStep::check(StrandContext const& ctx, std::shared_ptr<const SLE> const& sleSrc)
const
{
// Since this is a payment, MPToken must be present. Perform all
// MPToken related checks.
@@ -336,16 +337,15 @@ MPTEndpointPaymentStep::check(StrandContext const& ctx, AccountRoot<ReadView> co
// requireAuth checks if MPTIssuance exist. Note that issuer to issuer
// payment is invalid
auto const& issuer = mptIssue_.getIssuer();
auto const issuance = MPTokenIssuance(ctx.view, mptIssue_);
if (src_ != issuer)
{
if (auto const ter = issuance.requireAuth(src_); !isTesSuccess(ter))
if (auto const ter = requireAuth(ctx.view, mptIssue_, src_); !isTesSuccess(ter))
return ter;
}
if (dst_ != issuer)
{
if (auto const ter = issuance.requireAuth(dst_); !isTesSuccess(ter))
if (auto const ter = requireAuth(ctx.view, mptIssue_, dst_); !isTesSuccess(ter))
return ter;
}
@@ -358,10 +358,11 @@ MPTEndpointPaymentStep::check(StrandContext const& ctx, AccountRoot<ReadView> co
{
auto const& holder = ctx.isFirst ? src_ : dst_;
// Payment between the holders
if (issuance.isFrozen(holder))
if (isFrozen(ctx.view, holder, mptIssue_))
return tecLOCKED;
if (auto const ter = issuance.canTransfer(holder, ctx.strandDst); !isTesSuccess(ter))
if (auto const ter = canTransfer(ctx.view, mptIssue_, holder, ctx.strandDst);
!isTesSuccess(ter))
return ter;
}
// Don't need to check if a payment is between issuer and holder
@@ -392,7 +393,7 @@ MPTEndpointPaymentStep::check(StrandContext const& ctx, AccountRoot<ReadView> co
}
TER
MPTEndpointOfferCrossingStep::check(StrandContext const& ctx, AccountRoot<ReadView> const&)
MPTEndpointOfferCrossingStep::check(StrandContext const& ctx, std::shared_ptr<const SLE> const&)
{
return tesSUCCESS;
}
@@ -407,8 +408,7 @@ 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::WMPTokenIssuance(view, mptIssue_, j_).checkCreateMPT(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);
@@ -757,9 +757,8 @@ MPTEndpointStep<TDerived>::qualitiesSrcIssues(
"MPTEndpointStep<TDerived>::qualitiesSrcIssues : verify prev step debt "
"direction");
std::uint32_t const srcQOut = redeems(prevStepDebtDirection)
? MPTokenIssuance(sb, mptIssue_).transferRate().value
: QUALITY_ONE;
std::uint32_t const srcQOut =
redeems(prevStepDebtDirection) ? transferRate(sb, mptIssue_.getMptID()).value : QUALITY_ONE;
// Unlike trustline, MPT doesn't have line quality field
return {srcQOut, QUALITY_ONE};
@@ -831,8 +830,8 @@ MPTEndpointStep<TDerived>::check(StrandContext const& ctx) const
return temBAD_PATH;
}
AccountRoot const srcRoot(src_, ctx.view);
if (!srcRoot.exists())
auto const sleSrc = ctx.view.read(keylet::account(src_));
if (!sleSrc)
{
JLOG(j_.warn()) << "MPTEndpointStep: can't receive MPT from non-existent issuer: " << src_;
return terNO_ACCOUNT;
@@ -842,7 +841,7 @@ MPTEndpointStep<TDerived>::check(StrandContext const& ctx) const
if (!(ctx.isLast && ctx.isFirst))
{
auto const& account = ctx.isFirst ? src_ : dst_;
if (MPTokenIssuance(ctx.view, mptIssue_).isFrozen(account))
if (isFrozen(ctx.view, account, mptIssue_))
return terLOCKED;
}
@@ -887,7 +886,7 @@ MPTEndpointStep<TDerived>::check(StrandContext const& ctx) const
return temBAD_PATH;
}
return static_cast<TDerived const*>(this)->check(ctx, srcRoot);
return static_cast<TDerived const*>(this)->check(ctx, sleSrc);
}
template <class TDerived>

View File

@@ -122,8 +122,7 @@ accountFundsHelper(
{
if (id == asset.getIssuer())
{
return toAmount<T>(
MPTokenIssuance(view, asset.get<MPTIssue>()).issuerFundsToSelfIssue());
return toAmount<T>(issuerFundsToSelfIssue(view, asset.get<MPTIssue>()));
}
}

View File

@@ -119,8 +119,7 @@ protected:
XRPAmount
xrpLiquidImpl(ReadView& sb, std::int32_t reserveReduction) const
{
AccountRoot const account(acc_, sb, j_);
return account.xrpLiquid(reserveReduction);
return xrpl::xrpLiquid(sb, acc_, reserveReduction, j_);
}
std::string
@@ -344,8 +343,8 @@ XRPEndpointStep<TDerived>::check(StrandContext const& ctx) const
return temBAD_PATH;
}
AccountRoot const acctRoot(acc_, ctx.view);
if (!acctRoot)
auto sleAcc = ctx.view.read(keylet::account(acc_));
if (!sleAcc)
{
JLOG(j_.warn()) << "XRPEndpointStep: can't send or receive XRP from "
"non-existent account: "

View File

@@ -9,7 +9,6 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
@@ -227,12 +226,12 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
AccountID const account{ctx.tx[sfAccount]};
AccountID const dst{ctx.tx[sfDestination]};
AccountRoot const acctDst(dst, ctx.view);
auto sleDst = ctx.view.read(keylet::account(dst));
if (!acctDst)
if (!sleDst)
return tecNO_DST;
if (acctDst->isFlag(lsfRequireDestTag) && !ctx.tx[~sfDestinationTag])
if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx[~sfDestinationTag])
return tecDST_TAG_NEEDED;
// If credentials are provided - check them anyway
@@ -244,21 +243,21 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
if (!ctx.tx.isFieldPresent(sfCredentialIDs))
{
// Check whether the destination account requires deposit authorization.
if (acctDst->isFlag(lsfDepositAuth))
if (sleDst->isFlag(lsfDepositAuth))
{
if (!ctx.view.exists(keylet::depositPreauth(dst, account)))
return tecNO_PERMISSION;
}
}
AccountRoot const acctSrc(account, ctx.view);
XRPL_ASSERT(acctSrc, "xrpl::AccountDelete::preclaim : non-null account");
if (!acctSrc)
auto sleAccount = ctx.view.read(keylet::account(account));
XRPL_ASSERT(sleAccount, "xrpl::AccountDelete::preclaim : non-null account");
if (!sleAccount)
return terNO_ACCOUNT;
// If an issuer has any issued NFTs resident in the ledger then it
// cannot be deleted.
if (acctSrc->at(~sfMintedNFTokens) != acctSrc->at(~sfBurnedNFTokens))
if ((*sleAccount)[~sfMintedNFTokens] != (*sleAccount)[~sfBurnedNFTokens])
return tecHAS_OBLIGATIONS;
// If the account owns any NFTs it cannot be deleted.
@@ -277,7 +276,7 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
// We look at the account's Sequence rather than the transaction's
// Sequence in preparation for Tickets.
static constexpr std::uint32_t kSeqDelta{255};
if (acctSrc->getFieldU32(sfSequence) + kSeqDelta > ctx.view.seq())
if ((*sleAccount)[sfSequence] + kSeqDelta > ctx.view.seq())
return tecTOO_SOON;
// We don't allow an account to be deleted if
@@ -291,8 +290,8 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
// their account and mints a NFToken, it is possible that the
// NFTokenSequence of this NFToken is the same as the one that the
// authorized minter minted in a previous ledger.
if (acctSrc->at(~sfFirstNFTokenSequence).value_or(0) +
acctSrc->at(~sfMintedNFTokens).value_or(0) + kSeqDelta >
if ((*sleAccount)[~sfFirstNFTokenSequence].value_or(0) +
(*sleAccount)[~sfMintedNFTokens].value_or(0) + kSeqDelta >
ctx.view.seq())
return tecTOO_SOON;
@@ -345,19 +344,20 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
TER
AccountDelete::doApply()
{
WAccountRoot src(accountID_, view(), j_);
XRPL_ASSERT(src.exists(), "xrpl::AccountDelete::doApply : non-null source account");
auto src = view().peek(keylet::account(accountID_));
XRPL_ASSERT(src, "xrpl::AccountDelete::doApply : non-null source account");
auto const dstID = ctx_.tx[sfDestination];
WAccountRoot dst(dstID, view(), j_);
XRPL_ASSERT(dst.exists(), "xrpl::AccountDelete::doApply : non-null destination account");
auto dst = view().peek(keylet::account(dstID));
XRPL_ASSERT(dst, "xrpl::AccountDelete::doApply : non-null destination account");
if (!src.exists() || !dst.exists())
if (!src || !dst)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
if (ctx_.tx.isFieldPresent(sfCredentialIDs))
{
if (auto err = verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dst, ctx_.journal);
if (auto err =
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dstID, dst, ctx_.journal);
!isTesSuccess(err))
return err;
}
@@ -410,8 +410,8 @@ AccountDelete::doApply()
if (remainingBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
dst->clearFlag(lsfPasswordSpent);
dst.update();
src.erase();
view().update(dst);
view().erase(src);
return tesSUCCESS;
}

View File

@@ -6,7 +6,6 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DelegateHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/protocol/Feature.h>
@@ -223,8 +222,8 @@ AccountSet::preclaim(PreclaimContext const& ctx)
{
auto const id = ctx.tx[sfAccount];
AccountRoot const acctRoot(id, ctx.view);
if (!acctRoot)
auto const sle = ctx.view.read(keylet::account(id));
if (!sle)
return terNO_ACCOUNT;
std::uint32_t const uSetFlag = ctx.tx.getFieldU32(sfSetFlag);
@@ -235,7 +234,7 @@ AccountSet::preclaim(PreclaimContext const& ctx)
//
// RequireAuth
//
if (bSetRequireAuth && !acctRoot->isFlag(lsfRequireAuth))
if (bSetRequireAuth && !sle->isFlag(lsfRequireAuth))
{
if (!dirIsEmpty(ctx.view, keylet::ownerDir(id)))
{
@@ -251,7 +250,7 @@ AccountSet::preclaim(PreclaimContext const& ctx)
{
if (uSetFlag == asfAllowTrustLineClawback)
{
if (acctRoot->isFlag(lsfNoFreeze))
if (sle->isFlag(lsfNoFreeze))
{
JLOG(ctx.j.trace()) << "Can't set Clawback if NoFreeze is set";
return tecNO_PERMISSION;
@@ -266,7 +265,7 @@ AccountSet::preclaim(PreclaimContext const& ctx)
else if (uSetFlag == asfNoFreeze)
{
// Cannot set NoFreeze if clawback is enabled
if (acctRoot->isFlag(lsfAllowTrustLineClawback))
if (sle->isFlag(lsfAllowTrustLineClawback))
{
JLOG(ctx.j.trace()) << "Can't set NoFreeze if clawback is enabled";
return tecNO_PERMISSION;
@@ -280,10 +279,11 @@ AccountSet::preclaim(PreclaimContext const& ctx)
TER
AccountSet::doApply()
{
if (!account_)
auto const sle = view().peek(keylet::account(accountID_));
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
std::uint32_t const uFlagsIn = account_->getFieldU32(sfFlags);
std::uint32_t const uFlagsIn = sle->getFieldU32(sfFlags);
std::uint32_t uFlagsOut = uFlagsIn;
STTx const& tx{ctx_.tx};
@@ -314,13 +314,13 @@ AccountSet::doApply()
//
// RequireAuth
//
if (bSetRequireAuth && !account_->isFlag(lsfRequireAuth))
if (bSetRequireAuth && !sle->isFlag(lsfRequireAuth))
{
JLOG(j_.trace()) << "Set RequireAuth.";
uFlagsOut |= lsfRequireAuth;
}
if (bClearRequireAuth && account_->isFlag(lsfRequireAuth))
if (bClearRequireAuth && sle->isFlag(lsfRequireAuth))
{
JLOG(j_.trace()) << "Clear RequireAuth.";
uFlagsOut &= ~lsfRequireAuth;
@@ -329,13 +329,13 @@ AccountSet::doApply()
//
// RequireDestTag
//
if (bSetRequireDest && !account_->isFlag(lsfRequireDestTag))
if (bSetRequireDest && !sle->isFlag(lsfRequireDestTag))
{
JLOG(j_.trace()) << "Set lsfRequireDestTag.";
uFlagsOut |= lsfRequireDestTag;
}
if (bClearRequireDest && account_->isFlag(lsfRequireDestTag))
if (bClearRequireDest && sle->isFlag(lsfRequireDestTag))
{
JLOG(j_.trace()) << "Clear lsfRequireDestTag.";
uFlagsOut &= ~lsfRequireDestTag;
@@ -344,13 +344,13 @@ AccountSet::doApply()
//
// DisallowXRP
//
if (bSetDisallowXRP && !account_->isFlag(lsfDisallowXRP))
if (bSetDisallowXRP && !sle->isFlag(lsfDisallowXRP))
{
JLOG(j_.trace()) << "Set lsfDisallowXRP.";
uFlagsOut |= lsfDisallowXRP;
}
if (bClearDisallowXRP && account_->isFlag(lsfDisallowXRP))
if (bClearDisallowXRP && sle->isFlag(lsfDisallowXRP))
{
JLOG(j_.trace()) << "Clear lsfDisallowXRP.";
uFlagsOut &= ~lsfDisallowXRP;
@@ -359,7 +359,7 @@ AccountSet::doApply()
//
// DisableMaster
//
if ((uSetFlag == asfDisableMaster) && !account_->isFlag(lsfDisableMaster))
if ((uSetFlag == asfDisableMaster) && !sle->isFlag(lsfDisableMaster))
{
if (!sigWithMaster)
{
@@ -367,8 +367,7 @@ AccountSet::doApply()
return tecNEED_MASTER_KEY;
}
if ((!account_->isFieldPresent(sfRegularKey)) &&
(!view().peek(keylet::signers(accountID_))))
if ((!sle->isFieldPresent(sfRegularKey)) && (!view().peek(keylet::signers(accountID_))))
{
// Account has no regular key or multi-signer signer list.
return tecNO_ALTERNATIVE_KEY;
@@ -378,7 +377,7 @@ AccountSet::doApply()
uFlagsOut |= lsfDisableMaster;
}
if ((uClearFlag == asfDisableMaster) && account_->isFlag(lsfDisableMaster))
if ((uClearFlag == asfDisableMaster) && sle->isFlag(lsfDisableMaster))
{
JLOG(j_.trace()) << "Clear lsfDisableMaster.";
uFlagsOut &= ~lsfDisableMaster;
@@ -403,7 +402,7 @@ AccountSet::doApply()
//
if (uSetFlag == asfNoFreeze)
{
if (!sigWithMaster && !account_->isFlag(lsfDisableMaster))
if (!sigWithMaster && !sle->isFlag(lsfDisableMaster))
{
JLOG(j_.trace()) << "Must use master key to set NoFreeze.";
return tecNEED_MASTER_KEY;
@@ -433,16 +432,16 @@ AccountSet::doApply()
//
// Track transaction IDs signed by this account in its root
//
if ((uSetFlag == asfAccountTxnID) && !account_->isFieldPresent(sfAccountTxnID))
if ((uSetFlag == asfAccountTxnID) && !sle->isFieldPresent(sfAccountTxnID))
{
JLOG(j_.trace()) << "Set AccountTxnID.";
account_->makeFieldPresent(sfAccountTxnID);
sle->makeFieldPresent(sfAccountTxnID);
}
if ((uClearFlag == asfAccountTxnID) && account_->isFieldPresent(sfAccountTxnID))
if ((uClearFlag == asfAccountTxnID) && sle->isFieldPresent(sfAccountTxnID))
{
JLOG(j_.trace()) << "Clear AccountTxnID.";
account_->makeFieldAbsent(sfAccountTxnID);
sle->makeFieldAbsent(sfAccountTxnID);
}
//
@@ -469,12 +468,12 @@ AccountSet::doApply()
if (!uHash)
{
JLOG(j_.trace()) << "unset email hash";
account_->makeFieldAbsent(sfEmailHash);
sle->makeFieldAbsent(sfEmailHash);
}
else
{
JLOG(j_.trace()) << "set email hash";
account_->setFieldH128(sfEmailHash, uHash);
sle->setFieldH128(sfEmailHash, uHash);
}
}
@@ -488,12 +487,12 @@ AccountSet::doApply()
if (!uHash)
{
JLOG(j_.trace()) << "unset wallet locator";
account_->makeFieldAbsent(sfWalletLocator);
sle->makeFieldAbsent(sfWalletLocator);
}
else
{
JLOG(j_.trace()) << "set wallet locator";
account_->setFieldH256(sfWalletLocator, uHash);
sle->setFieldH256(sfWalletLocator, uHash);
}
}
@@ -507,12 +506,12 @@ AccountSet::doApply()
if (messageKey.empty())
{
JLOG(j_.debug()) << "clear message key";
account_->makeFieldAbsent(sfMessageKey);
sle->makeFieldAbsent(sfMessageKey);
}
else
{
JLOG(j_.debug()) << "set message key";
account_->setFieldVL(sfMessageKey, messageKey);
sle->setFieldVL(sfMessageKey, messageKey);
}
}
@@ -526,12 +525,12 @@ AccountSet::doApply()
if (domain.empty())
{
JLOG(j_.trace()) << "unset domain";
account_->makeFieldAbsent(sfDomain);
sle->makeFieldAbsent(sfDomain);
}
else
{
JLOG(j_.trace()) << "set domain";
account_->setFieldVL(sfDomain, domain);
sle->setFieldVL(sfDomain, domain);
}
}
@@ -545,12 +544,12 @@ AccountSet::doApply()
if (uRate == 0 || uRate == QUALITY_ONE)
{
JLOG(j_.trace()) << "unset transfer rate";
account_->makeFieldAbsent(sfTransferRate);
sle->makeFieldAbsent(sfTransferRate);
}
else
{
JLOG(j_.trace()) << "set transfer rate";
account_->setFieldU32(sfTransferRate, uRate);
sle->setFieldU32(sfTransferRate, uRate);
}
}
@@ -563,21 +562,21 @@ AccountSet::doApply()
if ((uTickSize == 0) || (uTickSize == Quality::kMaxTickSize))
{
JLOG(j_.trace()) << "unset tick size";
account_->makeFieldAbsent(sfTickSize);
sle->makeFieldAbsent(sfTickSize);
}
else
{
JLOG(j_.trace()) << "set tick size";
account_->setFieldU8(sfTickSize, uTickSize);
sle->setFieldU8(sfTickSize, uTickSize);
}
}
// Configure authorized minting account:
if (uSetFlag == asfAuthorizedNFTokenMinter)
account_->setAccountID(sfNFTokenMinter, ctx_.tx[sfNFTokenMinter]);
sle->setAccountID(sfNFTokenMinter, ctx_.tx[sfNFTokenMinter]);
if (uClearFlag == asfAuthorizedNFTokenMinter && account_->isFieldPresent(sfNFTokenMinter))
account_->makeFieldAbsent(sfNFTokenMinter);
if (uClearFlag == asfAuthorizedNFTokenMinter && sle->isFieldPresent(sfNFTokenMinter))
sle->makeFieldAbsent(sfNFTokenMinter);
if (uSetFlag == asfDisallowIncomingNFTokenOffer)
{
@@ -636,9 +635,9 @@ AccountSet::doApply()
}
if (uFlagsIn != uFlagsOut)
account_->setFieldU32(sfFlags, uFlagsOut);
sle->setFieldU32(sfFlags, uFlagsOut);
account_.update();
ctx_.view().update(sle);
return tesSUCCESS;
}

View File

@@ -3,7 +3,6 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/PublicKey.h>
@@ -28,8 +27,9 @@ SetRegularKey::calculateBaseFee(ReadView const& view, STTx const& tx)
{
if (calcAccountID(PublicKey(makeSlice(spk))) == id)
{
AccountRoot const acct(id, view);
if (acct && !acct->isFlag(lsfPasswordSpent))
auto const sle = view.read(keylet::account(id));
if (sle && !sle->isFlag(lsfPasswordSpent))
{
// flag is armed and they signed with the right account
return XRPAmount{0};
@@ -55,26 +55,27 @@ SetRegularKey::preflight(PreflightContext const& ctx)
TER
SetRegularKey::doApply()
{
if (!account_)
auto const sle = view().peek(keylet::account(accountID_));
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
if (!minimumFee(ctx_.registry, ctx_.baseFee, view().fees(), view().flags()))
account_->setFlag(lsfPasswordSpent);
sle->setFlag(lsfPasswordSpent);
if (ctx_.tx.isFieldPresent(sfRegularKey))
{
account_->setAccountID(sfRegularKey, ctx_.tx.getAccountID(sfRegularKey));
sle->setAccountID(sfRegularKey, ctx_.tx.getAccountID(sfRegularKey));
}
else
{
// Account has disabled master key and no multi-signer signer list.
if (account_->isFlag(lsfDisableMaster) && !view().peek(keylet::signers(accountID_)))
if (sle->isFlag(lsfDisableMaster) && !view().peek(keylet::signers(accountID_)))
return tecNO_ALTERNATIVE_KEY;
account_->makeFieldAbsent(sfRegularKey);
sle->makeFieldAbsent(sfRegularKey);
}
account_.update();
ctx_.view().update(sle);
return tesSUCCESS;
}

View File

@@ -180,11 +180,11 @@ static TER
removeSignersFromLedger(
ServiceRegistry& registry,
ApplyView& view,
AccountID const& account,
Keylet const& accountKeylet,
Keylet const& ownerDirKeylet,
Keylet const& signerListKeylet,
beast::Journal j)
{
auto const ownerDirKeylet = keylet::ownerDir(account);
auto const signerListKeylet = keylet::signers(account);
// We have to examine the current SignerList so we know how much to
// reduce the OwnerCount.
SLE::pointer const signers = view.peek(signerListKeylet);
@@ -215,8 +215,8 @@ removeSignersFromLedger(
// LCOV_EXCL_STOP
}
WAccountRoot wrappedAcct(account, view, j);
wrappedAcct.adjustOwnerCount(removeFromOwnerCount);
adjustOwnerCount(
view, view.peek(accountKeylet), removeFromOwnerCount, registry.getJournal("View"));
view.erase(signers);
@@ -230,7 +230,12 @@ SignerListSet::removeFromLedger(
AccountID const& account,
beast::Journal j)
{
return removeSignersFromLedger(registry, view, account, j);
auto const accountKeylet = keylet::account(account);
auto const ownerDirKeylet = keylet::ownerDir(account);
auto const signerListKeylet = keylet::signers(account);
return removeSignersFromLedger(
registry, view, accountKeylet, ownerDirKeylet, signerListKeylet, j);
}
NotTEC
@@ -295,18 +300,23 @@ SignerListSet::validateQuorumAndSignerEntries(
TER
SignerListSet::replaceSignerList()
{
auto const accountKeylet = keylet::account(accountID_);
auto const ownerDirKeylet = keylet::ownerDir(accountID_);
auto const signerListKeylet = keylet::signers(accountID_);
// This may be either a create or a replace. Preemptively remove any
// old signer list. May reduce the reserve, so this is done before
// checking the reserve.
if (TER const ter = removeSignersFromLedger(ctx_.registry, view(), accountID_, j_))
if (TER const ter = removeSignersFromLedger(
ctx_.registry, view(), accountKeylet, ownerDirKeylet, signerListKeylet, j_))
return ter;
WAccountRoot wrappedAcct(accountID_, view(), j_);
if (!wrappedAcct)
auto const sle = view().peek(accountKeylet);
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
// Compute new reserve. Verify the account has funds to meet the reserve.
std::uint32_t const oldOwnerCount{(*wrappedAcct)[sfOwnerCount]};
std::uint32_t const oldOwnerCount{(*sle)[sfOwnerCount]};
static constexpr int kAddedOwnerCount = 1;
std::uint32_t const flags{lsfOneOwnerCount};
@@ -320,12 +330,11 @@ SignerListSet::replaceSignerList()
return tecINSUFFICIENT_RESERVE;
// Everything's ducky. Add the ltSIGNER_LIST to the ledger.
Keylet const ownerDirKeylet = keylet::ownerDir(accountID_);
Keylet const signerListKeylet = keylet::signers(accountID_);
auto signerList = std::make_shared<SLE>(signerListKeylet);
view().insert(signerList);
writeSignersToSLE(signerList, flags);
auto viewJ = ctx_.registry.get().getJournal("View");
// Add the signer list to the account's directory.
auto const page =
ctx_.view().dirInsert(ownerDirKeylet, signerListKeylet, describeOwnerDir(accountID_));
@@ -340,22 +349,27 @@ SignerListSet::replaceSignerList()
// If we succeeded, the new entry counts against the
// creator's reserve.
wrappedAcct.adjustOwnerCount(kAddedOwnerCount);
adjustOwnerCount(view(), sle, kAddedOwnerCount, viewJ);
return tesSUCCESS;
}
TER
SignerListSet::destroySignerList()
{
auto const accountKeylet = keylet::account(accountID_);
// Destroying the signer list is only allowed if either the master key
// is enabled or there is a regular key.
if (!account_)
SLE::pointer const ledgerEntry = view().peek(accountKeylet);
if (!ledgerEntry)
return tefINTERNAL; // LCOV_EXCL_LINE
if ((account_->isFlag(lsfDisableMaster)) && (!account_->isFieldPresent(sfRegularKey)))
if ((ledgerEntry->isFlag(lsfDisableMaster)) && (!ledgerEntry->isFieldPresent(sfRegularKey)))
return tecNO_ALTERNATIVE_KEY;
return removeSignersFromLedger(ctx_.registry, view(), accountID_, j_);
auto const ownerDirKeylet = keylet::ownerDir(accountID_);
auto const signerListKeylet = keylet::signers(accountID_);
return removeSignersFromLedger(
ctx_.registry, view(), accountKeylet, ownerDirKeylet, signerListKeylet, j_);
}
void

View File

@@ -124,12 +124,13 @@ checkAttestationPublicKey(
AccountID const accountFromPK = calcAccountID(pk);
if (AccountRoot const acctSigner(attestationSignerAccount, view); acctSigner)
if (auto const sleAttestationSigningAccount =
view.read(keylet::account(attestationSignerAccount)))
{
if (accountFromPK == attestationSignerAccount)
{
// master key
if (acctSigner->isFlag(lsfDisableMaster))
if (sleAttestationSigningAccount->isFlag(lsfDisableMaster))
{
JLOG(j.trace()) << "Attempt to add an attestation with "
"disabled master key.";
@@ -139,7 +140,8 @@ checkAttestationPublicKey(
else
{
// regular key
if (std::optional<AccountID> const regularKey = acctSigner->at(~sfRegularKey);
if (std::optional<AccountID> const regularKey =
(*sleAttestationSigningAccount)[~sfRegularKey];
regularKey != accountFromPK)
{
if (!regularKey)
@@ -401,12 +403,12 @@ transferHelper(
if (dst == src)
return tesSUCCESS;
AccountRoot const acctDst(dst, psb);
if (acctDst)
auto const dstK = keylet::account(dst);
if (auto sleDst = psb.read(dstK))
{
// Check dst tag and deposit auth
if (acctDst->isFlag(lsfRequireDestTag) && !dstTag)
if (sleDst->isFlag(lsfRequireDestTag) && !dstTag)
return tecDST_TAG_NEEDED;
// If the destination is the claim owner, and this is a claim
@@ -415,7 +417,7 @@ transferHelper(
bool const canBypassDepositAuth =
dst == claimOwner && depositAuthPolicy == DepositAuthPolicy::DstCanBypass;
if (!canBypassDepositAuth && acctDst->isFlag(lsfDepositAuth) &&
if (!canBypassDepositAuth && sleDst->isFlag(lsfDepositAuth) &&
!psb.exists(keylet::depositPreauth(dst, src)))
{
return tecNO_PERMISSION;
@@ -428,17 +430,17 @@ transferHelper(
if (amt.native())
{
WAccountRoot acctSrc(src, psb, j);
XRPL_ASSERT(acctSrc, "xrpl::transferHelper : non-null source account");
if (!acctSrc)
auto const sleSrc = psb.peek(keylet::account(src));
XRPL_ASSERT(sleSrc, "xrpl::transferHelper : non-null source account");
if (!sleSrc)
return tecINTERNAL; // LCOV_EXCL_LINE
{
auto const ownerCount = acctSrc->getFieldU32(sfOwnerCount);
auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
auto const reserve = psb.fees().accountReserve(ownerCount);
auto const availableBalance = [&]() -> STAmount {
STAmount curBal = acctSrc->at(sfBalance);
STAmount curBal = (*sleSrc)[sfBalance];
// Checking that account == src and postFeeBalance == curBal is
// not strictly necessary, but helps protect against future
// changes
@@ -454,8 +456,8 @@ transferHelper(
}
}
WAccountRoot acctDst(dst, psb, j);
if (!acctDst)
auto sleDst = psb.peek(dstK);
if (!sleDst)
{
if (canCreate == CanCreateDstPolicy::No)
{
@@ -469,17 +471,17 @@ transferHelper(
}
// Create the account.
acctDst.newSLE();
acctDst->setAccountID(sfAccount, dst);
acctDst->setFieldU32(sfSequence, psb.seq());
sleDst = std::make_shared<SLE>(dstK);
sleDst->setAccountID(sfAccount, dst);
sleDst->setFieldU32(sfSequence, psb.seq());
acctDst.insert();
psb.insert(sleDst);
}
acctSrc->at(sfBalance) = acctSrc->at(sfBalance) - amt;
acctDst->at(sfBalance) = acctDst->at(sfBalance) + amt;
acctSrc.update();
acctDst.update();
(*sleSrc)[sfBalance] = (*sleSrc)[sfBalance] - amt;
(*sleDst)[sfBalance] = (*sleDst)[sfBalance] + amt;
psb.update(sleSrc);
psb.update(sleDst);
return tesSUCCESS;
}
@@ -715,7 +717,7 @@ finalizeClaimHelper(
auto const cidOwner = (*sleClaimID)[sfAccount];
{
// Remove the claim id
WAccountRoot wrappedOwner(cidOwner, outerSb, j);
auto const sleOwner = outerSb.peek(keylet::account(cidOwner));
auto const page = (*sleClaimID)[sfOwnerNode];
if (!outerSb.dirRemove(keylet::ownerDir(cidOwner), page, sleClaimID->key(), true))
{
@@ -727,7 +729,7 @@ finalizeClaimHelper(
// Remove the claim id from the ledger
outerSb.erase(sleClaimID);
wrappedOwner.adjustOwnerCount(-1);
adjustOwnerCount(outerSb, sleOwner, -1, j);
}
}
@@ -750,9 +752,9 @@ getSignersListAndQuorum(ReadView const& view, SLE const& sleBridge, beast::Journ
std::uint32_t q = std::numeric_limits<std::uint32_t>::max();
AccountID const thisDoor = sleBridge[sfAccount];
AccountRoot const acctDoor(thisDoor, view);
auto const sleDoor = [&] { return view.read(keylet::account(thisDoor)); }();
if (!acctDoor)
if (!sleDoor)
{
return {r, q, tecINTERNAL};
}
@@ -1131,13 +1133,14 @@ applyCreateAccountAttestations(
return tecDIR_FULL; // LCOV_EXCL_LINE
(*createdSleClaimID)[sfOwnerNode] = *page;
WAccountRoot wrappedDoor(doorAccount, psb, j);
if (!wrappedDoor)
auto const sleDoor = psb.peek(doorK);
if (!sleDoor)
return tecINTERNAL; // LCOV_EXCL_LINE
// Reserve was already checked
wrappedDoor.adjustOwnerCount(1);
adjustOwnerCount(psb, sleDoor, 1, j);
psb.insert(createdSleClaimID);
psb.update(sleDoor);
}
psb.apply(rawView);
@@ -1415,25 +1418,25 @@ XChainCreateBridge::preclaim(PreclaimContext const& ctx)
if (!isXRP(bridgeSpec.issue(chainType)))
{
AccountRoot const acctIssuer(bridgeSpec.issue(chainType).account, ctx.view);
auto const sleIssuer = ctx.view.read(keylet::account(bridgeSpec.issue(chainType).account));
if (!acctIssuer)
if (!sleIssuer)
return tecNO_ISSUER;
// Allowing clawing back funds would break the bridge's invariant that
// wrapped funds are always backed by locked funds
if (acctIssuer->isFlag(lsfAllowTrustLineClawback))
if (sleIssuer->isFlag(lsfAllowTrustLineClawback))
return tecNO_PERMISSION;
}
{
// Check reserve
AccountRoot const acctSrc(account, ctx.view);
if (!acctSrc)
auto const sleAcc = ctx.view.read(keylet::account(account));
if (!sleAcc)
return terNO_ACCOUNT;
auto const balance = acctSrc->at(sfBalance);
auto const reserve = ctx.view.fees().accountReserve(acctSrc->at(sfOwnerCount) + 1);
auto const balance = (*sleAcc)[sfBalance];
auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
@@ -1450,8 +1453,8 @@ XChainCreateBridge::doApply()
auto const reward = ctx_.tx[sfSignatureReward];
auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
WAccountRoot wrappedAcct(account, ctx_.view(), j_);
if (!wrappedAcct)
auto const sleAcct = ctx_.view().peek(keylet::account(account));
if (!sleAcct)
return tecINTERNAL; // LCOV_EXCL_LINE
STXChainBridge::ChainType const chainType =
@@ -1478,10 +1481,10 @@ XChainCreateBridge::doApply()
(*sleBridge)[sfOwnerNode] = *page;
}
wrappedAcct.adjustOwnerCount(1);
adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
ctx_.view().insert(sleBridge);
wrappedAcct.update();
ctx_.view().update(sleAcct);
return tesSUCCESS;
}
@@ -1561,7 +1564,8 @@ BridgeModify::doApply()
auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
bool const clearAccountCreate = ctx_.tx.isFlag(tfClearAccountCreateAmount);
if (!AccountRoot(account, ctx_.view(), j_))
auto const sleAcct = ctx_.view().peek(keylet::account(account));
if (!sleAcct)
return tecINTERNAL; // LCOV_EXCL_LINE
STXChainBridge::ChainType const chainType =
@@ -1618,7 +1622,7 @@ XChainClaim::preclaim(PreclaimContext const& ctx)
return tecNO_ENTRY;
}
if (!AccountRoot(ctx.tx[sfDestination], ctx.view, ctx.j))
if (!ctx.view.read(keylet::account(ctx.tx[sfDestination])))
{
return tecNO_DST;
}
@@ -1701,6 +1705,7 @@ XChainClaim::doApply()
{
PaymentSandbox psb(&ctx_.view());
AccountID const account = ctx_.tx[sfAccount];
auto const dst = ctx_.tx[sfDestination];
STXChainBridge const bridgeSpec = ctx_.tx[sfXChainBridge];
STAmount const& thisChainAmount = ctx_.tx[sfAmount];
@@ -1722,10 +1727,11 @@ XChainClaim::doApply()
// `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
// views, it's important that the sle's lifetime doesn't overlap.
auto const sleAcct = psb.peek(keylet::account(account));
auto const sleBridge = peekBridge(psb, bridgeSpec);
auto const sleClaimID = psb.peek(claimIDKeylet);
if (!(sleBridge && sleClaimID && account_))
if (!(sleBridge && sleClaimID && sleAcct))
return Unexpected(tecINTERNAL);
AccountID const thisDoor = (*sleBridge)[sfAccount];
@@ -1793,7 +1799,7 @@ XChainClaim::doApply()
bridgeSpec,
dst,
dstTag,
/*claimOwner*/ accountID_,
/*claimOwner*/ account,
sendingAmount,
rewardPoolSrc,
signatureReward,
@@ -1898,10 +1904,12 @@ XChainCommit::doApply()
{
PaymentSandbox psb(&ctx_.view());
auto const account = ctx_.tx[sfAccount];
auto const amount = ctx_.tx[sfAmount];
auto const bridgeSpec = ctx_.tx[sfXChainBridge];
if (!account_)
auto const sleAccount = psb.read(keylet::account(account));
if (!sleAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleBridge = readBridge(psb, bridgeSpec);
@@ -1914,11 +1922,11 @@ XChainCommit::doApply()
TransferHelperSubmittingAccountInfo submittingAccountInfo{
.account = accountID_,
.preFeeBalance = preFeeBalance_,
.postFeeBalance = (*account_)[sfBalance]};
.postFeeBalance = (*sleAccount)[sfBalance]};
auto const thTer = transferHelper(
psb,
accountID_,
account,
dst,
/*dstTag*/ std::nullopt,
/*claimOwner*/ std::nullopt,
@@ -1971,12 +1979,12 @@ XChainCreateClaimID::preclaim(PreclaimContext const& ctx)
{
// Check reserve
AccountRoot const acctSrc(account, ctx.view);
if (!acctSrc)
auto const sleAcc = ctx.view.read(keylet::account(account));
if (!sleAcc)
return terNO_ACCOUNT;
auto const balance = acctSrc->at(sfBalance);
auto const reserve = ctx.view.fees().accountReserve(acctSrc->at(sfOwnerCount) + 1);
auto const balance = (*sleAcc)[sfBalance];
auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
@@ -1993,8 +2001,8 @@ XChainCreateClaimID::doApply()
auto const reward = ctx_.tx[sfSignatureReward];
auto const otherChainSrc = ctx_.tx[sfOtherChainSource];
WAccountRoot wrappedAcct(account, ctx_.view(), j_);
if (!wrappedAcct)
auto const sleAcct = ctx_.view().peek(keylet::account(account));
if (!sleAcct)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec);
@@ -2035,10 +2043,11 @@ XChainCreateClaimID::doApply()
(*sleClaimID)[sfOwnerNode] = *page;
}
wrappedAcct.adjustOwnerCount(1);
adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
ctx_.view().insert(sleClaimID);
ctx_.view().update(sleBridge);
ctx_.view().update(sleAcct);
return tesSUCCESS;
}
@@ -2171,11 +2180,13 @@ XChainCreateAccountCommit::doApply()
{
PaymentSandbox psb(&ctx_.view());
AccountID const account = ctx_.tx[sfAccount];
STAmount const amount = ctx_.tx[sfAmount];
STAmount const reward = ctx_.tx[sfSignatureReward];
STXChainBridge const bridge = ctx_.tx[sfXChainBridge];
if (!account_)
auto const sle = psb.peek(keylet::account(account));
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleBridge = peekBridge(psb, bridge);
@@ -2188,11 +2199,11 @@ XChainCreateAccountCommit::doApply()
TransferHelperSubmittingAccountInfo submittingAccountInfo{
.account = accountID_,
.preFeeBalance = preFeeBalance_,
.postFeeBalance = (*account_)[sfBalance]};
.postFeeBalance = (*sle)[sfBalance]};
STAmount const toTransfer = amount + reward;
auto const thTer = transferHelper(
psb,
accountID_,
account,
dst,
/*dstTag*/ std::nullopt,
/*claimOwner*/ std::nullopt,

View File

@@ -66,6 +66,8 @@ CheckCancel::doApply()
AccountID const srcId{sleCheck->getAccountID(sfAccount)};
AccountID const dstId{sleCheck->getAccountID(sfDestination)};
auto viewJ = ctx_.registry.get().getJournal("View");
// If the check is not written to self (and it shouldn't be), remove the
// check from the destination account root.
if (srcId != dstId)
@@ -91,8 +93,8 @@ CheckCancel::doApply()
}
// If we succeeded, update the check owner's reserve.
WAccountRoot wrappedSrc(srcId, view(), j_);
wrappedSrc.adjustOwnerCount(-1);
auto const sleSrc = view().peek(keylet::account(srcId));
adjustOwnerCount(view(), sleSrc, -1, viewJ);
// Remove check from ledger.
view().erase(sleCheck);

View File

@@ -106,16 +106,16 @@ CheckCash::preclaim(PreclaimContext const& ctx)
// LCOV_EXCL_STOP
}
{
AccountRoot const acctSrc(srcId, ctx.view);
AccountRoot const acctDst(dstId, ctx.view);
if (!acctSrc || !acctDst)
auto const sleSrc = ctx.view.read(keylet::account(srcId));
auto const sleDst = ctx.view.read(keylet::account(dstId));
if (!sleSrc || !sleDst)
{
// If the check exists this should never occur.
JLOG(ctx.j.warn()) << "Malformed transaction: source or destination not in ledger";
return tecNO_ENTRY;
}
if (acctDst->isFlag(lsfRequireDestTag) && !sleCheck->isFieldPresent(sfDestinationTag))
if (sleDst->isFlag(lsfRequireDestTag) && !sleCheck->isFieldPresent(sfDestinationTag))
{
// The tag is basically account-specific information we don't
// understand, but we can require someone to fill it in.
@@ -190,8 +190,8 @@ CheckCash::preclaim(PreclaimContext const& ctx)
auto const sleTrustLine =
ctx.view.read(keylet::line(dstId, issuerId, currency));
auto const issuer = AccountRoot{issuerId, ctx.view};
if (!issuer)
auto const sleIssuer = ctx.view.read(keylet::account(issuerId));
if (!sleIssuer)
{
JLOG(ctx.j.warn()) << "Can't receive IOUs from "
"non-existent issuer: "
@@ -199,7 +199,7 @@ CheckCash::preclaim(PreclaimContext const& ctx)
return tecNO_ISSUER;
}
if (issuer->isFlag(lsfRequireAuth))
if (sleIssuer->isFlag(lsfRequireAuth))
{
if (!sleTrustLine)
{
@@ -233,7 +233,7 @@ CheckCash::preclaim(PreclaimContext const& ctx)
// However, the trustline from destination to issuer may
// not be frozen.
if (IOUIssuance(ctx.view, issuerId, currency).isFrozen(dstId))
if (isFrozen(ctx.view, dstId, currency, issuerId))
{
JLOG(ctx.j.warn()) << "Cashing a check to a frozen trustline.";
return tecFROZEN;
@@ -242,8 +242,8 @@ CheckCash::preclaim(PreclaimContext const& ctx)
return tesSUCCESS;
},
[&](MPTIssue const& issue) -> TER {
auto const issuer = AccountRoot{issuerId, ctx.view};
if (!issuer)
auto const sleIssuer = ctx.view.read(keylet::account(issuerId));
if (!sleIssuer)
{
JLOG(ctx.j.warn()) << "Can't receive MPTs from "
"non-existent issuer: "
@@ -251,15 +251,14 @@ CheckCash::preclaim(PreclaimContext const& ctx)
return tecNO_ISSUER;
}
auto const issuance = MPTokenIssuance(ctx.view, issue);
if (auto const err = issuance.requireAuth(dstId, AuthType::WeakAuth);
if (auto const err = requireAuth(ctx.view, issue, dstId, AuthType::WeakAuth);
!isTesSuccess(err))
{
JLOG(ctx.j.warn()) << "Cashing a check to a MPT requiring auth.";
return err;
}
if (issuance.isFrozen(dstId))
if (isFrozen(ctx.view, dstId, issue))
{
JLOG(ctx.j.warn()) << "Cashing a check to a frozen MPT.";
return tecLOCKED;
@@ -331,8 +330,7 @@ CheckCash::doApply()
// from src's directory, we allow them to send that additional
// incremental reserve amount in the transfer. Hence the -1
// argument.
WAccountRoot const wrappedSrc(srcId, psb, j_);
STAmount const srcLiquid{wrappedSrc.xrpLiquid(-1)};
STAmount const srcLiquid{xrpLiquid(psb, srcId, -1, viewJ)};
// Now, how much do they need in order to be successful?
STAmount const xrpDeliver{
@@ -384,19 +382,19 @@ CheckCash::doApply()
// Check reserve. Return destination account SLE if enough reserve,
// otherwise return nullptr.
auto checkReserve = [&]() -> std::optional<WAccountRoot> {
WAccountRoot dst(accountID_, psb, j_);
auto checkReserve = [&]() -> std::shared_ptr<SLE> {
auto sleDst = psb.peek(keylet::account(accountID_));
// Can the account cover the trust line's or MPT reserve?
if (std::uint32_t const ownerCount = {dst->at(sfOwnerCount)};
if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)};
preFeeBalance_ < psb.fees().accountReserve(ownerCount + 1))
{
JLOG(j_.trace()) << "Trust line does not exist. "
"Insufficient reserve to create line.";
return std::nullopt;
return nullptr;
}
return dst;
return sleDst;
};
std::optional<Keylet> trustLineKey;
@@ -423,37 +421,36 @@ CheckCash::doApply()
// a. this (destination) account and
// b. issuing account (not sending account).
auto dstOpt = checkReserve();
if (!dstOpt)
auto const sleDst = checkReserve();
if (sleDst == nullptr)
return tecNO_LINE_INSUF_RESERVE;
auto& dst = *dstOpt;
Currency const& currency = issue.currency;
STAmount initialBalance(flowDeliver.asset());
initialBalance.get<Issue>().account = noAccount();
if (TER const ter = WIOUIssuance(psb, deliverIssuer, currency, viewJ)
.trustCreate(
destLow, // is dest low?
deliverIssuer, // source
accountID_, // destination
trustLineKey->key, // ledger index
dst, // Account to add to
false, // authorize account
!dst->isFlag(lsfDefaultRipple), //
false, // freeze trust line
false, // deep freeze trust line
initialBalance, // zero initial balance
Issue(currency, accountID_), // limit of zero
0, // quality in
0, // quality out
viewJ); // journal
if (TER const ter = trustCreate(
psb, // payment sandbox
destLow, // is dest low?
deliverIssuer, // source
accountID_, // destination
trustLineKey->key, // ledger index
sleDst, // Account to add to
false, // authorize account
!sleDst->isFlag(lsfDefaultRipple), //
false, // freeze trust line
false, // deep freeze trust line
initialBalance, // zero initial balance
Issue(currency, accountID_), // limit of zero
0, // quality in
0, // quality out
viewJ); // journal
!isTesSuccess(ter))
{
return ter;
}
dst.update();
psb.update(sleDst);
// Note that we _don't_ need to be careful about
// destroying the trust line if the check cashing
@@ -482,19 +479,18 @@ CheckCash::doApply()
return std::nullopt;
},
[&](MPTIssue const& issue) -> std::optional<TER> {
if (account_ != deliverIssuer)
if (accountID_ != deliverIssuer)
{
auto const& mptID = issue.getMptID();
// Create MPT if it doesn't exist
auto const mptokenKey = keylet::mptoken(mptID, accountID_);
if (!psb.exists(mptokenKey))
{
auto dst = checkReserve();
if (!dst.has_value())
auto sleDst = checkReserve();
if (sleDst == nullptr)
return tecINSUFFICIENT_RESERVE;
if (auto const err =
WMPTokenIssuance(psb, mptID, j_).checkCreateMPT(accountID_, j_);
if (auto const err = checkCreateMPT(psb, mptID, accountID_, j_);
!isTesSuccess(err))
{
return err;
@@ -580,8 +576,7 @@ CheckCash::doApply()
}
// If we succeeded, update the check owner's reserve.
WAccountRoot wrappedSrc(srcId, psb, j_);
wrappedSrc.adjustOwnerCount(-1);
adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), -1, viewJ);
// Remove check from ledger.
psb.erase(sleCheck);

View File

@@ -77,27 +77,27 @@ CheckCreate::preflight(PreflightContext const& ctx)
TER
CheckCreate::preclaim(PreclaimContext const& ctx)
{
AccountID const srcId{ctx.tx[sfAccount]};
AccountID const dstId{ctx.tx[sfDestination]};
AccountRoot const acctDst(dstId, ctx.view);
if (!acctDst)
AccountID const srcId{ctx.tx[sfAccount]};
auto const sleDst = ctx.view.read(keylet::account(dstId));
if (!sleDst)
{
JLOG(ctx.j.warn()) << "Destination account does not exist.";
return tecNO_DST;
}
// Check if the destination has disallowed incoming checks
if (acctDst->isFlag(lsfDisallowIncomingCheck))
if (sleDst->isFlag(lsfDisallowIncomingCheck))
return tecNO_PERMISSION;
// Pseudo-accounts cannot cash checks. Note, this is not amendment-gated
// because all writes to pseudo-account discriminator fields **are**
// amendment gated, hence the behaviour of this check will always match the
// currently active amendments.
if (acctDst.isPseudoAccount())
if (isPseudoAccount(sleDst))
return tecNO_PERMISSION;
if (acctDst->isFlag(lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag))
if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag))
{
// The tag is basically account-specific information we don't
// understand, but we can require someone to fill it in.
@@ -152,10 +152,9 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
return std::nullopt;
},
[&](MPTIssue const& issue) -> std::optional<TER> {
auto const issuance = MPTokenIssuance(ctx.view, issue);
if (srcId != issuerId && issuance.isFrozen(srcId))
if (srcId != issuerId && isFrozen(ctx.view, srcId, issue))
return tecLOCKED;
if (dstId != issuerId && issuance.isFrozen(dstId))
if (dstId != issuerId && isFrozen(ctx.view, dstId, issue))
return tecLOCKED;
return std::nullopt;
@@ -176,15 +175,15 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
TER
CheckCreate::doApply()
{
if (!account_)
auto const sle = view().peek(keylet::account(accountID_));
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
// 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(account_->getFieldU32(sfOwnerCount) + 1)};
STAmount const reserve{view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
@@ -212,6 +211,7 @@ CheckCreate::doApply()
view().insert(sleCheck);
auto viewJ = ctx_.registry.get().getJournal("View");
// If it's not a self-send (and it shouldn't be), add Check to the
// destination's owner directory.
if (dstAccountId != accountID_)
@@ -241,7 +241,7 @@ CheckCreate::doApply()
sleCheck->setFieldU64(sfOwnerNode, *page);
}
// If we succeeded, the new entry counts against the creator's reserve.
account_.adjustOwnerCount(1);
adjustOwnerCount(view(), sle, 1, viewJ);
return tesSUCCESS;
}

View File

@@ -89,15 +89,15 @@ CredentialAccept::doApply()
AccountID const issuer{ctx_.tx[sfIssuer]};
// Both exist as credential object exist itself (checked in preclaim)
WAccountRoot wrappedSubject(accountID_, view(), j_);
WAccountRoot wrappedIssuer(issuer, view(), j_);
auto const sleSubject = view().peek(keylet::account(accountID_));
auto const sleIssuer = view().peek(keylet::account(issuer));
if (!wrappedSubject || !wrappedIssuer)
if (!sleSubject || !sleIssuer)
return tefINTERNAL; // LCOV_EXCL_LINE
{
STAmount const reserve{
view().fees().accountReserve(wrappedSubject->getFieldU32(sfOwnerCount) + 1)};
view().fees().accountReserve(sleSubject->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
@@ -119,8 +119,8 @@ CredentialAccept::doApply()
sleCred->setFieldU32(sfFlags, lsfAccepted);
view().update(sleCred);
wrappedIssuer.adjustOwnerCount(-1);
wrappedSubject.adjustOwnerCount(1);
adjustOwnerCount(view(), sleIssuer, -1, j_);
adjustOwnerCount(view(), sleSubject, 1, j_);
return tesSUCCESS;
}

View File

@@ -126,13 +126,13 @@ CredentialCreate::doApply()
sleCred->setFieldU32(sfExpiration, *optExp);
}
WAccountRoot wrappedIssuer(accountID_, view(), j_);
if (!wrappedIssuer)
auto const sleIssuer = view().peek(keylet::account(accountID_));
if (!sleIssuer)
return tefINTERNAL; // LCOV_EXCL_LINE
{
STAmount const reserve{
view().fees().accountReserve(wrappedIssuer->getFieldU32(sfOwnerCount) + 1)};
view().fees().accountReserve(sleIssuer->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
@@ -153,7 +153,7 @@ CredentialCreate::doApply()
return tecDIR_FULL;
sleCred->setFieldU64(sfIssuerNode, *page);
wrappedIssuer.adjustOwnerCount(1);
adjustOwnerCount(view(), sleIssuer, 1, j_);
}
if (subject == accountID_)

View File

@@ -1,6 +1,7 @@
#include <xrpl/tx/transactors/delegate/DelegateSet.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
@@ -67,8 +68,8 @@ DelegateSet::preclaim(PreclaimContext const& ctx)
TER
DelegateSet::doApply()
{
WAccountRoot wrappedOwner(accountID_, ctx_.view(), j_);
if (!wrappedOwner)
auto const sleOwner = ctx_.view().peek(keylet::account(accountID_));
if (!sleOwner)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const& authAccount = ctx_.tx[sfAuthorize];
@@ -94,7 +95,7 @@ DelegateSet::doApply()
return tecINTERNAL; // LCOV_EXCL_LINE
STAmount const reserve{
ctx_.view().fees().accountReserve(wrappedOwner->getFieldU32(sfOwnerCount) + 1)};
ctx_.view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
@@ -105,7 +106,7 @@ DelegateSet::doApply()
sle->setFieldArray(sfPermissions, permissions);
// Add to delegating account's owner directory.
// Add to delegating account's owner directory
auto const page = ctx_.view().dirInsert(
keylet::ownerDir(accountID_), delegateKey, describeOwnerDir(accountID_));
@@ -125,7 +126,7 @@ DelegateSet::doApply()
(*sle)[sfDestinationNode] = *destPage;
ctx_.view().insert(sle);
wrappedOwner.adjustOwnerCount(1);
adjustOwnerCount(ctx_.view(), sleOwner, 1, ctx_.journal);
return tesSUCCESS;
}
@@ -161,11 +162,11 @@ DelegateSet::deleteDelegate(ApplyView& view, std::shared_ptr<SLE> const& sle, be
}
// Only the delegating account's owner count was incremented on creation
WAccountRoot wrappedOwner(delegator, view, j);
if (!wrappedOwner)
auto const sleOwner = view.peek(keylet::account(delegator));
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
wrappedOwner.adjustOwnerCount(-1);
adjustOwnerCount(view, sleOwner, -1, j);
view.erase(sle);

View File

@@ -7,7 +7,6 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AMMCore.h>
@@ -121,7 +120,7 @@ AMMBid::preclaim(PreclaimContext const& ctx)
{
for (auto const& account : ctx.tx.getFieldArray(sfAuthAccounts))
{
if (AccountRoot const acct(account[sfAccount], ctx.view); !acct)
if (!ctx.view.read(keylet::account(account[sfAccount])))
{
JLOG(ctx.j.debug()) << "AMM Bid: Invalid Account.";
return terNO_ACCOUNT;
@@ -178,14 +177,14 @@ AMMBid::preclaim(PreclaimContext const& ctx)
}
static std::pair<TER, bool>
applyBid(ApplyContext& ctx, Sandbox& sb, AccountID const& accountId, beast::Journal j)
applyBid(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Journal j)
{
using namespace std::chrono;
auto const ammSle = sb.peek(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
if (!ammSle)
return {tecINTERNAL, false};
STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
auto const lpTokens = ammLPHolds(sb, *ammSle, accountId, ctx.journal);
auto const lpTokens = ammLPHolds(sb, *ammSle, account, ctx.journal);
auto const& rules = ctx.view().rules();
if (!rules.enabled(fixInnerObjTemplate))
{
@@ -217,11 +216,11 @@ applyBid(ApplyContext& ctx, Sandbox& sb, AccountID const& accountId, beast::Jour
auto validOwner = [&](AccountID const& account) {
// Valid range is 0-19 but the tailing slot pays MinSlotPrice
// and doesn't refund so the check is < instead of <= to optimize.
return timeSlot && *timeSlot < kTailingSlot && AccountRoot(account, sb, j);
return timeSlot && *timeSlot < kTailingSlot && sb.read(keylet::account(account));
};
auto updateSlot = [&](std::uint32_t fee, Number const& minPrice, Number const& burn) -> TER {
auctionSlot.setAccountID(sfAccount, accountId);
auctionSlot.setAccountID(sfAccount, account);
auctionSlot.setFieldU32(sfExpiration, current + kTotalTimeSlotSecs);
if (fee != 0)
{
@@ -252,8 +251,7 @@ applyBid(ApplyContext& ctx, Sandbox& sb, AccountID const& accountId, beast::Jour
return tecINTERNAL;
// LCOV_EXCL_STOP
}
auto res = WIOUIssuance(sb, lpTokens.get<Issue>(), ctx.journal)
.redeem(accountId, saBurn, ctx.journal);
auto res = redeemIOU(sb, account, saBurn, lpTokens.get<Issue>(), ctx.journal);
if (!isTesSuccess(res))
{
JLOG(ctx.journal.debug()) << "AMM Bid: failed to redeem.";
@@ -352,11 +350,7 @@ applyBid(ApplyContext& ctx, Sandbox& sb, AccountID const& accountId, beast::Jour
return {tecINTERNAL, false};
}
res = accountSend(
sb,
accountId,
auctionSlot[sfAccount],
toSTAmount(lpTokens.asset(), refund),
ctx.journal);
sb, account, auctionSlot[sfAccount], toSTAmount(lpTokens.asset(), refund), ctx.journal);
if (!isTesSuccess(res))
{
JLOG(ctx.journal.debug()) << "AMM Bid: failed to refund.";

Some files were not shown because too many files have changed in this diff Show More