Compare commits

...

542 Commits

Author SHA1 Message Date
TimothyBanks
543366c1fb fix: Merge upstream branch 2026-07-28 13:40:06 -04:00
TimothyBanks
5820b9cad7 fix: Merge upstream branch 2026-07-28 13:30:00 -04:00
TimothyBanks
338eb50814 fix: Merge upstream branch 2026-07-28 13:01:50 -04:00
TimothyBanks
de2a913ad4 fix: Addressing code review comments 2026-07-27 12:41:27 -04:00
Mayukha Vadari
75fac65e27 change definitions config 2026-07-27 12:21:08 -04:00
Mayukha Vadari
ecae6b6346 Merge pull request #7866 from XRPLF/mvadari/smart-escrow-merge-july
chore: Update `ripple/smart-escrow` branch from base
2026-07-27 09:12:19 -04:00
Mayukha Vadari
6f4fe178dd fix tests 2026-07-24 19:29:26 -04:00
Mayukha Vadari
083986ab82 fix clang-tidy issues 2026-07-24 14:16:36 -04:00
Mayukha Vadari
c145201538 fix tests 2026-07-24 14:15:39 -04:00
Mayukha Vadari
4fbbd6ff59 fix build 2026-07-24 12:03:43 -04:00
Mayukha Vadari
39c95fee3b Merge branch 'ripple/se/fees' of https://github.com/XRPLF/rippled into ripple/smart-escrow 2026-07-24 11:23:01 -04:00
Mayukha Vadari
db6fe77959 Merge pull request #7857 from XRPLF/mvadari/se-fees-update-july
chore: Update `ripple/se/fees` branch from its base
2026-07-24 10:11:38 -04:00
Mayukha Vadari
be58ec0cd5 fix build 2026-07-23 17:04:05 -04:00
Mayukha Vadari
4073838f66 Merge branch 'ripple/se/wasmi-tests' of https://github.com/XRPLF/rippled into ripple/se/fees 2026-07-23 16:40:44 -04:00
Mayukha Vadari
e227e2ce8b Merge pull request #7855 from XRPLF/mvadari/wasmi-tests-update-july
chore: Update `ripple/se/wasmi-tests` to the latest
2026-07-23 16:34:13 -04:00
TimothyBanks
2d8fad840a feat: Add some performance tests for escrow wasm functions 2026-07-23 15:05:57 -04:00
Mayukha Vadari
383a2f8819 comment out broken tests, leave todo to fix 2026-07-23 14:09:44 -04:00
Mayukha Vadari
e64703b151 fix most tests 2026-07-23 13:54:46 -04:00
Mayukha Vadari
c91640e3ac fix build 2026-07-23 12:37:46 -04:00
Mayukha Vadari
0ceee3a013 Merge branch 'ripple/wasmi-host-functions' of https://github.com/XRPLF/rippled into ripple/se/wasmi-tests 2026-07-23 12:35:51 -04:00
Mayukha Vadari
974b17f427 Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2026-07-23 12:29:03 -04:00
Mayukha Vadari
91de196312 Merge pull request #7834 from XRPLF/ripple/se/hf-merge-july
chore: Merge in from `ripple/wasmi`
2026-07-23 12:28:38 -04:00
Mayukha Vadari
ac71553abe Merge pull request #7851 from kuznetsss/Update_hf-merge-july
chore: Update hf merge july
2026-07-23 11:30:09 -04:00
Mayukha Vadari
9a8ec56d42 Merge branch 'develop' into ripple/wasmi 2026-07-23 11:29:49 -04:00
Timothy Banks
bc6455b8b9 Merge branch 'ripple/smart-escrow' into smart-escrow/perf-test 2026-07-23 10:12:39 -04:00
TimothyBanks
ca97c98b68 feat: Add some performance tests for escrow wasm functions 2026-07-23 09:39:41 -04:00
Sergey Kuznetsov
cb6dad0bc4 chore: Fix clang-tidy issues 2026-07-23 14:10:40 +01:00
Sergey Kuznetsov
d54ca4cd3b Merge develop 2026-07-23 13:15:00 +01:00
Sergey Kuznetsov
74cfb3586a ci: Update CI image (#7850) 2026-07-23 11:21:49 +00:00
Ayaz Salikhov
0072ced94c ci: Update XRPLF/actions (#7849) 2026-07-23 10:22:18 +00:00
Mayukha Vadari
5a0269aca7 fix test 2026-07-22 18:37:10 -04:00
Mayukha Vadari
0774ca32a9 fix build 2026-07-22 18:05:56 -04:00
Mayukha Vadari
8feb75011e Merge remote-tracking branch 'upstream/ripple/wasmi-host-functions' into ripple/se/hf-merge-july 2026-07-22 16:17:14 -04:00
Mayukha Vadari
088c5b3b03 Merge remote-tracking branch 'upstream/ripple/wasmi' into ripple/se/hf-merge-july 2026-07-22 16:12:12 -04:00
TimothyBanks
26f9488b5d feat: Add some performance tests for escrow wasm functions 2026-07-22 15:59:52 -04:00
TimothyBanks
fca18f59cd feat: Add some performance tests for escrow wasm functions 2026-07-22 15:57:08 -04:00
Mayukha Vadari
1c4abe8c5e refactor: Remove WasmNumber class (#7833) 2026-07-22 15:56:33 -04:00
Mayukha Vadari
12ed506565 chore: Clean up grammar in PR template (#7846) 2026-07-22 18:50:10 +00:00
Ayaz Salikhov
3122de86bf build: Create versioned compiler/tooling symlinks in nix environments (#7844) 2026-07-22 17:04:02 +00:00
Sergey Kuznetsov
7edf39e622 ci: Use rust-overlay to bring Rust into nix (#7837) 2026-07-22 14:00:17 +00:00
Mayukha Vadari
91a5747bf9 Merge branch 'ripple/wasmi-host-functions' into ripple/se/hf-merge-july 2026-07-21 15:16:33 -04:00
Mayukha Vadari
bc892f0bd3 feat: Change function name from finish to escrow_finish (#7840) 2026-07-21 15:12:36 -04:00
dependabot[bot]
ba01b05f33 ci: [DEPENDABOT] bump actions/setup-python from 6.3.0 to 7.0.0 (#7830)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-21 16:04:45 +00:00
Sergey Kuznetsov
c414e1877d ci: Update pre-commit image (#7838) 2026-07-21 15:37:47 +00:00
Sergey Kuznetsov
f56b79f00d ci: Add cargo to pre-commit image (#7835) 2026-07-21 13:39:37 +00:00
Ayaz Salikhov
4918f3bb20 chore: Add doxygen in pre-commit image (#7836) 2026-07-21 13:38:26 +00:00
Mayukha Vadari
ee0a3dfad7 ci: Improve test debuggability (#7619)
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-07-21 13:14:40 +00:00
Sergey Kuznetsov
8950530b04 Merge branch 'develop' into ripple/wasmi 2026-07-21 12:39:31 +01:00
Mayukha Vadari
5cbebf3c58 fix clang-tidy issues 2026-07-20 18:23:10 -04:00
Mayukha Vadari
c634c75aee fix build issues 2026-07-20 18:07:42 -04:00
Mayukha Vadari
9801c23bad Merge branch 'ripple/wasmi' of https://github.com/XRPLF/rippled into ripple/wasmi-host-functions 2026-07-20 17:38:18 -04:00
Mayukha Vadari
f6472347e9 Merge branch 'develop' into ripple/wasmi 2026-07-20 17:33:13 -04:00
Bart
042c9660cd chore: Add CODEOWNERS for CI-related changes (#7832)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-07-20 21:06:24 +00:00
Ayaz Salikhov
60b49f3fe0 ci: Use in-house image for pre-commit (#7610) 2026-07-20 19:11:09 +00:00
Ayaz Salikhov
4c869ee16c ci: Build separate pre-commit docker image (#7831) 2026-07-20 17:40:38 +00:00
Rithvik Reddygari
a41889df31 refactor: SField and TER code renames (#7797) 2026-07-20 12:29:39 -04:00
Ayaz Salikhov
b2f45813e0 chore: Workaround boost compiler resolution inside Nix environment (#7826) 2026-07-20 14:04:08 +00:00
TimothyBanks
d1d3e3c72e feat: Run levelization 2026-07-16 17:14:45 -04:00
TimothyBanks
b9df9c9c45 feat: Dump metric values after test run 2026-07-16 12:57:10 -04:00
TimothyBanks
3bb6904946 feat: Dump metric values after test run 2026-07-16 12:45:46 -04:00
Vito Tumas
5ce0b1c2c7 refactor: Restructure LendingHelpers to improve readability (#7807) 2026-07-16 15:47:56 +00:00
Andrzej Budzanowski
701311f27e test: Add google benchmark dependency and migrate nodestore timing test as a benchmark (#7317)
Co-authored-by: Marek Foss <marek.foss@neti-soft.com>
Co-authored-by: Alex Kremer <akremer@ripple.com>
2026-07-16 15:44:43 +00:00
Sergey Kuznetsov
b1a670c46e ci: Add Rust to CI (#7808)
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-07-16 15:28:04 +00:00
TimothyBanks
d430010b36 feat: Add metrics around wasm calls 2026-07-16 11:22:13 -04:00
TimothyBanks
d88f7d8f68 feat: Use beast metrics 2026-07-16 11:11:32 -04:00
TimothyBanks
1825f38a10 fix: Add perf tests for escrow create and escrow finish 2026-07-10 23:06:22 -04:00
TimothyBanks
e70623d0cb fix: Add perf tests for escrow create and escrow finish 2026-07-10 16:43:30 -04:00
TimothyBanks
19631a6d92 fix: Add perf tests for escrow create and escrow finish 2026-07-10 16:21:53 -04:00
Rithvik Reddygari
2e48bb4b7f refactor: Rename sfield names, sfExtensionComputeLimit -> sfGasLimit, sfExtensionSizeLimit -> sfBytecodeSizeLimit (#7708) 2026-07-08 16:26:58 -04:00
Sergey Kuznetsov
9e427ae3ba Merge develop 2026-07-08 14:24:21 +01: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
4a01fe5aa6 Merge branch 'ripple/se/wasmi-tests' into ripple/se/fees 2026-06-09 17:11:35 -04:00
Mayukha Vadari
f60a9f1c64 Revert "remove wasm engine tests"
This reverts commit 5ee903befc.
2026-06-09 17:10:13 -04:00
Mayukha Vadari
5ee903befc remove wasm engine tests 2026-06-09 17:09:45 -04:00
Mayukha Vadari
68f0dcae53 feat: Add fee vote maxes (#7346)
This PR adds hard-cap maxes for the size and compute limits, currently set at twice the defaults.
2026-06-04 16:25:52 -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
d3550fac60 Merge pull request #7303 from XRPLF/mvadari/se-escrow-update
chore: Update branch from develop
2026-05-27 14:12:19 -04:00
Mayukha Vadari
10ef15fff1 fix clang-tidy 2026-05-27 10:08:52 -04:00
Mayukha Vadari
2307cfdc5a fix clang-tidy 2026-05-26 20:09:46 -04:00
Mayukha Vadari
3b328cfc45 fix broken builds 2026-05-26 18:53:18 -04:00
Mayukha Vadari
e20b2430ba update gas amounts 2026-05-26 18:29:28 -04:00
Mayukha Vadari
99072fbddf one more 2026-05-26 17:59:03 -04:00
Mayukha Vadari
059cefa35a fix typos 2026-05-26 17:58:38 -04:00
Mayukha Vadari
a2205190a6 fix build 2026-05-26 17:57:28 -04:00
Mayukha Vadari
4c657bc92e fix build 2026-05-20 17:06:43 -04:00
Mayukha Vadari
ac481a0bd7 fix clang-tidy 2026-05-20 16:47:52 -04:00
Mayukha Vadari
7eacd9d735 Merge remote-tracking branch 'upstream/ripple/se/fees' into mvadari/se-escrow-update 2026-05-20 16:43:57 -04:00
Mayukha Vadari
1f9675d22f Merge pull request #7301 from XRPLF/ripple/se/fees-fix-tidy
chore: Update branch from `develop`
2026-05-20 16:36:42 -04:00
Mayukha Vadari
029fa435ee fix issues 2026-05-20 14:21:56 -04:00
Mayukha Vadari
c92ca426bf fix clang-tidy 2026-05-20 14:13:53 -04:00
Mayukha Vadari
ff896b226a fix more build issues 2026-05-20 14:10:38 -04:00
Mayukha Vadari
3c53c94e7a Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees-fix-tidy 2026-05-20 14:02:17 -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
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
Mayukha Vadari
36fb84926e Update src/test/app/FeeVote_test.cpp
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-04-14 13:00:18 -04:00
Mayukha Vadari
56decaf852 Update cfg/xrpld-example.cfg
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-04-13 17:08:07 -04:00
Mayukha Vadari
d562129e7c Merge branch 'ripple/se/fees' into ripple/smart-escrow 2026-04-13 17:01:46 -04:00
Mayukha Vadari
1d9fe9dbaf Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2026-04-13 17:01:20 -04:00
Olek
8cc2169939 test: Calling wrap functions from c++ side (#6699) 2026-04-09 18:48:58 -04:00
Mayukha Vadari
74beb5f80d Merge branch 'ripple/se/fees' of https://github.com/XRPLF/rippled into ripple/smart-escrow 2026-04-08 13:52:24 -04:00
Mayukha Vadari
a4dfdaf77a Merge branch 'ripple/wasmi-host-functions' of https://github.com/XRPLF/rippled into ripple/se/fees 2026-04-08 13:51:41 -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
Mayukha Vadari
4bae4d781f fix build 2026-04-08 11:43:21 -04:00
Mayukha Vadari
01a5e07830 Merge branch 'ripple/se/fees' of https://github.com/XRPLF/rippled into ripple/smart-escrow 2026-04-08 09:58:40 -04:00
Mayukha Vadari
34522bc668 Merge branch 'ripple/wasmi-host-functions' of https://github.com/XRPLF/rippled into ripple/se/fees 2026-04-08 09:58:24 -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
fe2e5ce649 fix clang-tidy issues 2026-04-03 12:33:14 -04:00
Mayukha Vadari
6afa51142d Merge branch 'ripple/se/fees' of https://github.com/XRPLF/rippled into ripple/smart-escrow 2026-04-03 11:06:16 -04:00
Mayukha Vadari
22054a573d Merge branch 'ripple/wasmi-host-functions' of https://github.com/XRPLF/rippled into ripple/se/fees 2026-04-03 09:57:23 -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
0acfa1c4f2 fix merge issues 2026-04-02 19:22:52 -04:00
Mayukha Vadari
309fdfc3f2 Merge branch 'ripple/se/fees' of https://github.com/XRPLF/rippled into ripple/smart-escrow 2026-04-02 19:08:08 -04:00
Mayukha Vadari
63bd5fc4ee fix clang-tidy issues 2026-04-02 19:07:10 -04:00
Mayukha Vadari
82e7f7eeec Merge branch 'ripple/wasmi-host-functions' of https://github.com/XRPLF/rippled into ripple/se/fees 2026-04-02 19:06:00 -04:00
Mayukha Vadari
c5598a4284 fix clang-tidy issues 2026-04-02 19:05:34 -04:00
Mayukha Vadari
63d09f9c51 Merge branch 'ripple/wasmi-host-functions' of https://github.com/XRPLF/rippled into ripple/se/fees 2026-04-02 19:04:34 -04:00
Mayukha Vadari
0deb6bcadf fix build 2026-04-02 18:39:14 -04:00
Mayukha Vadari
d606f88e84 Merge branch 'ripple/wasmi-host-functions' of https://github.com/XRPLF/rippled into ripple/se/fees 2026-04-02 18:12:30 -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
f8a149c675 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2026-03-24 09:10:37 -07:00
Mayukha Vadari
51458a92e2 Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2026-03-24 08:49:18 -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
Olek
780380da7e Modularization fixes (#6488) 2026-03-10 11:37:12 -04:00
Mayukha Vadari
654338fa66 Merge branch 'develop' into ripple/wasmi 2026-03-06 16:27:50 -04:00
Mayukha Vadari
fdfdf4fceb fix pre-commit 2026-03-05 14:49:07 -04:00
Mayukha Vadari
a76bd834b6 Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow [WIP, broken] 2026-03-05 14:45:30 -04:00
Mayukha Vadari
864d88a3c2 Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2026-03-05 13:48:44 -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
7004d216ed Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2026-03-03 11:19:02 -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
50a74b899d add defensive check for sfData length (#6449) 2026-03-02 14:47:47 -05:00
Mayukha Vadari
00eeab6d44 Merge branch 'wasmi-host-functions' into ripple/se/fees 2026-02-27 16:50:13 -05: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
5148098079 Merge branch 'wasmi-host-functions' into ripple/se/fees 2026-02-18 13:21:53 -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
Mayukha Vadari
4fe508cb92 fix comments 2026-02-12 17:48: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
446ad36cbb Merge branch 'ripple/se/fees' into ripple/smart-escrow 2026-02-10 17:47:32 -05:00
Mayukha Vadari
eb2d44d442 Merge branch 'wasmi-host-functions' into ripple/se/fees 2026-02-10 17:44:52 -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
fd14054f17 update preflight checks (#6094) 2026-02-09 17:36:02 -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
4318b2ebf7 fix build issue 2026-02-06 12:44:05 -05:00
Mayukha Vadari
e6ee492822 try again 2026-02-05 16:27:44 -05:00
Mayukha Vadari
d4510147d1 fix build issue 2026-02-05 14:42:14 -05:00
Mayukha Vadari
a9a94fbf1a fix tests 2026-02-05 13:24:50 -05:00
Mayukha Vadari
719ba392db Merge branch 'ripple/smart-escrow' into ripple/se/fees 2026-02-04 18:41:29 -05:00
Mayukha Vadari
cd46b5d999 Merge branch 'wasmi-host-functions' into ripple/se/fees 2026-02-04 18:41:19 -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
Mayukha Vadari
f01ac563a9 Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow 2026-02-04 18:07:12 -05:00
Mayukha Vadari
13707dda05 Merge remote-tracking branch 'upstream/ripple/wasmi-host-functions' into ripple/se/fees 2026-02-04 17:13:50 -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
a1844086d7 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2026-02-03 15:32:21 -05:00
Mayukha Vadari
f625fb993a Merge branch 'wasmi-host-functions' into ripple/se/fees 2026-02-03 15:19:36 -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
ac173b6827 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2026-01-28 17:14:13 -05:00
Mayukha Vadari
69c61b2235 fix merge issues 2026-01-28 16:52:27 -05:00
Mayukha Vadari
6ae0e860ff Merge remote-tracking branch 'upstream/ripple/wasmi-host-functions' into ripple/se/fees 2026-01-28 16:39:44 -05:00
Mayukha Vadari
c077e7f073 Merge commit '4eb34f3' into ripple/se/fees 2026-01-28 16:39:06 -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
Mayukha Vadari
430696682d Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow 2026-01-26 09:43:14 -05:00
Mayukha Vadari
4621e4eda3 Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2026-01-26 09:29:34 -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
981ac7abf4 simplify fee code (#6249)
* simplify lambda

* clean up fee code

* fix tests, better error handling

* simplify source_location
2026-01-23 15:14:24 -05:00
Mayukha Vadari
57d2a91ad5 test large WASM modules (#6206)
* [WIP] first attempt at large wasm test

* finish large WASM modules

* fix windows build (hopefully)

* Apply suggestions from code review

* respond to comments

* add file and line to fail

* clean up test

* add source_location

* simplify

* fix windows
2026-01-23 14:45:09 -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
Olek
94b35a234e Make hostfunctions object shared (#6252) 2026-01-21 15:27:06 -05:00
Mayukha Vadari
b5d0078927 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2026-01-21 13:26:37 -05:00
Mayukha Vadari
43c80edaf4 Merge branch 'wasmi-host-functions' into ripple/se/fees 2026-01-21 12:58:24 -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
Mayukha Vadari
9538e9b34c Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow 2026-01-15 14:34:54 -05:00
Mayukha Vadari
384b3608d7 Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2026-01-15 14:17:57 -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
pwang200
fb97f7b596 fix unit tests failed due to fuel changes (#6174)
* fix unit tests failed due to fuel changes

* fix tests

* fix tests

---------

Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
2026-01-13 16:48:25 -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
Mayukha Vadari
845c503ea6 Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow 2026-01-13 13:48:15 -05:00
Mayukha Vadari
e1513570df Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2026-01-13 13:46:50 -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
Mayukha Vadari
ff39fa59d9 Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow 2026-01-12 14:12:04 -05:00
Olek
d83ec96848 Switch to wasmi v1.0.6 (#6204) 2026-01-12 13:36:02 -05:00
Mayukha Vadari
f0d0739528 Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2026-01-12 13:28:07 -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
8015088340 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2026-01-08 11:46:18 -05:00
Mayukha Vadari
103379836a Merge branch 'wasmi-host-functions' into ripple/se/fees 2026-01-08 11:45:05 -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
Mayukha Vadari
61b2fe4f64 fix test 2026-01-06 17:58:22 -05:00
Mayukha Vadari
7ee964f514 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2026-01-06 17:22:42 -05:00
Mayukha Vadari
397bc8781e fix more merge issues 2026-01-06 14:27:46 -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
8bb8c2e38b Merge remote-tracking branch 'upstream/ripple/wasmi-host-functions' into ripple/se/fees 2026-01-06 13:40:53 -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
36ecd3b52b Merge remote-tracking branch 'upstream/ripple/wasmi-host-functions' into ripple/se/fees 2026-01-05 18:51:46 -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
6ffbef09c2 fix gas in test 2025-12-23 11:00:57 -08:00
Mayukha Vadari
e05f907788 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-12-22 17:02:12 -08:00
Mayukha Vadari
9d1f51b01a Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2025-12-22 17:01:34 -08: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
e916416642 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-12-15 10:47:26 -08:00
Mayukha Vadari
827ecc6e3a Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2025-12-15 10:44:17 -08:00
Mayukha Vadari
6a54ed7f14 fix fee overflow issue in EscrowFinish (#6130) 2025-12-12 15:12:43 -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
Olek
1e0741690d Fix sign cost (#6103) 2025-12-03 18:27:06 -05:00
Mayukha Vadari
6d2640871d Merge branch 'develop' into ripple/wasmi 2025-12-02 18:40:54 -05:00
Olek
c5d178f152 HF cost for smart escrow (#6097) 2025-12-02 13:12:15 -05:00
Mayukha Vadari
5a17940e2a Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-12-02 07:48:22 -05:00
Mayukha Vadari
27ac30208d Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2025-12-02 07:44:22 -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
abfcc4ef67 fix tests 2025-11-25 04:17:42 +05:30
Mayukha Vadari
e40a4df777 Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2025-11-25 03:49:06 +05:30
Mayukha Vadari
dba187f8c5 fix build issues 2025-11-25 03:42:38 +05:30
Mayukha Vadari
7a7b96107c Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2025-11-25 03:42:05 +05:30
Mayukha Vadari
8f2f8d53b4 update gas amounts for wasmi 2025-11-25 03:32:01 +05:30
Mayukha Vadari
49acc61961 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-11-25 03:31:10 +05:30
Olek
500bb68831 Fix win build (#6076) 2025-11-24 16:56:23 -05:00
Mayukha Vadari
95d78a8600 Merge branch 'wasmi-host-functions' into ripple/se/fees 2025-11-25 03:26:19 +05:30
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
def7758a23 remove copyright stuff 2025-11-04 17:53:13 -05:00
Mayukha Vadari
58e5b4ad25 Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow 2025-11-04 17:52:17 -05:00
Mayukha Vadari
578413859c Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-11-04 17:52:00 -05:00
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
fa8aa49376 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-11-04 14:51:11 -05:00
Mayukha Vadari
3195eb16b2 Merge branch 'wamr-host-functions' into ripple/se/fees 2025-11-04 14:50:31 -05:00
Mayukha Vadari
7bf6878b4b fix imports 2025-11-04 14:49:45 -05:00
Mayukha Vadari
a891b49c67 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-11-04 14:24:51 -05:00
Mayukha Vadari
eed280d169 Merge branch 'wamr-host-functions' into ripple/se/fees 2025-11-04 13:37:09 -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
7e2e10f02c Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-24 18:01:33 -04:00
Mayukha Vadari
aebec5378c Merge branch 'wamr-host-functions' into ripple/se/fees 2025-10-24 18:01:17 -04:00
Mayukha Vadari
0c65a386b5 fix tests 2025-10-24 18:01:01 -04:00
Mayukha Vadari
22ca691e75 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-24 17:14:54 -04:00
Mayukha Vadari
fc6ff69752 fix tests 2025-10-24 17:14:46 -04:00
Mayukha Vadari
079e251aca fix bug 2025-10-24 16:52:17 -04:00
Mayukha Vadari
67e2c1b563 Merge branch 'wamr-host-functions' into ripple/se/fees 2025-10-24 16:06:02 -04:00
Mayukha Vadari
29f5430881 fix bug 2025-10-24 16:05:38 -04:00
Mayukha Vadari
b6bd268be2 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-24 16:02:23 -04:00
Mayukha Vadari
209ee25c32 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-24 16:02:16 -04:00
Mayukha Vadari
101f285bcd return size from updateData 2025-10-24 16:01:45 -04:00
Mayukha Vadari
566b85b3d6 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-23 15:41:53 -04:00
Mayukha Vadari
af6beb1d7c fix ledger used for rules 2025-10-23 15:41:32 -04:00
Mayukha Vadari
7d22fe804d Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-23 15:38:46 -04:00
Mayukha Vadari
ce19c13059 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-23 15:38:37 -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
9cfb7ac340 fix build issue 2025-10-20 17:20:35 -04:00
Mayukha Vadari
3a0e9aab4f Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-20 14:51:15 -04:00
Mayukha Vadari
43caa1ef29 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-20 11:53:56 -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
Mayukha Vadari
3b6cd22e32 fix 2025-10-14 18:10:11 -04:00
Mayukha Vadari
c9c35780d2 reduce diff more 2025-10-14 18:00:58 -04:00
Mayukha Vadari
17f401f374 reduce diff 2025-10-14 17:58:41 -04:00
Mayukha Vadari
c6c54b3282 resolve todos 2025-10-14 17:56:12 -04:00
Mayukha Vadari
91455b6860 respond to comments 2025-10-13 15:20:03 -04:00
Olek
9e14c14a26 Use xrplf conan repo for wamr (#5862) 2025-10-13 15:11:21 -04:00
Mayukha Vadari
f16f243c22 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-13 14:01:11 -04:00
Mayukha Vadari
fe601308e7 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-13 13:58:09 -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
e41f6a71b7 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-09 17:13:13 -04:00
Mayukha Vadari
51f1be7f5b Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-09 17:10:52 -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
db263b696c Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-06 17:01:32 -04:00
Mayukha Vadari
f57b855d74 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-06 17:01:17 -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
86525d8583 test: add tests for fee voting (#5747) 2025-10-06 16:32:04 -04:00
Mayukha Vadari
c41e52f57a Move Smart Escrow tests to separate file (#5849) 2025-10-06 16:27:21 -04:00
Mayukha Vadari
55772a0d07 add sfData preflight checks + tests (#5839) 2025-10-02 17:50:43 -04:00
Mayukha Vadari
965a9e89ac Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow 2025-10-02 15:51:35 -04:00
Mayukha Vadari
51ee06429b Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-02 14:35:36 -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
ca85d09f02 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-09-30 14:43:24 -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
8d266d3941 remove STInt64 (#5815) 2025-09-29 15:43:10 -04:00
Mayukha Vadari
a865b4da1c Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-09-26 17:10:10 -04:00
Mayukha Vadari
e59f5f3b01 fix tests 2025-09-26 17:09:53 -04:00
Mayukha Vadari
8729688feb Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow 2025-09-26 16:56:51 -04:00
Mayukha Vadari
c8b06e7de1 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-09-26 16:50:34 -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
f1f798bb85 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-09-26 15:52:22 -04:00
Mayukha Vadari
c3fd52c177 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-09-26 15:51:56 -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
85bff20ae5 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-09-23 10:22:30 -04:00
Mayukha Vadari
737fab5471 fix issues 2025-09-22 23:55:57 -04:00
Mayukha Vadari
e6592e93a9 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-09-22 18:32:36 -04:00
Mayukha Vadari
5a6c4e8ae0 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-09-22 18:23:54 -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
Mayukha Vadari
7420f47658 SmartEscrow fee voting changes 2025-09-22 18:22:44 -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
2f869b3cfc Merge branch 'develop' into ripple/smart-escrow 2025-09-11 16:41:14 -04:00
Mayukha Vadari
ffa21c27a7 fix test 2025-09-11 16:40:59 -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
5c480cf883 Merge branch 'develop' into ripple/smart-escrow 2025-09-10 14:51:45 -04:00
Mayukha Vadari
45ab15d4b5 add WAMR dependency 2025-09-10 14:40:48 -04:00
Mayukha Vadari
adc64e7866 Merge branch 'develop' into ripple/smart-escrow 2025-09-10 10:46:51 -04:00
Mayukha Vadari
4d4a1cfe82 Merge branch 'develop' into ripple/smart-escrow 2025-09-09 16:19:27 -04:00
Mayukha Vadari
f2c7da3705 Merge branch 'develop' into ripple/smart-escrow 2025-09-09 14:51:25 -04:00
Mayukha Vadari
3ab0a82cd3 Merge branch 'develop' into ripple/smart-escrow 2025-09-08 15:20:01 -04:00
Mayukha Vadari
a46d772147 fix build and tests (#5768)
* fix conan.lock

* add conan.lock to triggers

* update on-trigger.yml too

* fix tests

* roll back unrelated changes
2025-09-04 17:05:39 -04:00
Mayukha Vadari
f3c50318e8 Merge branch 'develop' into ripple/smart-escrow 2025-09-04 13:42:59 -04:00
Mayukha Vadari
e7aa924c0e Merge branch 'develop' into ripple/smart-escrow 2025-09-03 15:54:57 -04:00
Mayukha Vadari
5266f04970 chore: rollback unrelated changes (#5737) 2025-09-02 18:26:01 -04:00
Mayukha Vadari
db957cf191 Merge branch 'develop' into ripple/smart-escrow 2025-08-29 16:54:17 -04:00
Mayukha Vadari
8ac514363d get new fees and reserves working (#5714) 2025-08-29 10:58:52 -04:00
Mayukha Vadari
c2ea68cca4 Merge branch 'develop' into ripple/smart-escrow 2025-08-28 14:02:10 -04:00
Mayukha Vadari
3d86881ce7 Merge branch 'develop' into ripple/smart-escrow 2025-08-27 13:58:38 -04:00
Mayukha Vadari
697d1470f4 change: adjust the function signatures for get_ledger_sqn and get_parent_ledger_time (#5733) 2025-08-27 13:58:27 -04:00
Mayukha Vadari
0b5f8f4051 Merge remote-tracking branch 'upstream/develop' into ripple/smart-escrow 2025-08-26 17:59:43 -04:00
Mayukha Vadari
0fed78fbcc Merge branch 'develop' into ripple/smart-escrow 2025-08-26 15:04:33 -04:00
Mayukha Vadari
8c38ef726b chore: exclude a bunch of code that doesn't need to be tested from codecov (#5721)
* exclude the bulk of HostFunc.h from codecov

* fix codecov (maybe)

* adjust

* more codecov excl
2025-08-26 15:04:27 -04:00
Mayukha Vadari
2399d90334 Merge branch 'develop' into ripple/smart-escrow 2025-08-25 10:42:48 -04:00
Mayukha Vadari
6367d68d1e Merge branch 'develop' into ripple/smart-escrow 2025-08-22 14:02:12 -04:00
Mayukha Vadari
155a84c8a3 Merge branch 'develop' into ripple/smart-escrow 2025-08-22 13:24:43 -04:00
Mayukha Vadari
10558c9eff Merge remote-tracking branch 'upstream/develop' into develop5.5 2025-08-22 10:38:10 -04:00
Mayukha Vadari
dd30d811e6 feat: last set of host functions (#5674) 2025-08-15 16:51:30 -04:00
Mayukha Vadari
293d8e4ddb test: store Rust source code in rippled, instead of just opaque hex strings (#5653) 2025-08-15 15:32:36 -04:00
Mayukha Vadari
77875c9133 fix: actually return int instead of bool (#5651) 2025-08-15 13:58:38 -04:00
Olek
647b47567e Fix float point binary format (#5688) 2025-08-15 09:51:40 -04:00
Olek
b0a1ad3b06 Disable float point instructions (#5679) 2025-08-14 14:59:55 -04:00
Mayukha Vadari
1d141bf2e8 chore: move WASM files to separate folder (#5666) 2025-08-14 11:46:10 -04:00
Olek
0d0e279ae2 Float point Hostfunctions unit tests (#5656)
* Added direct unittests for float hostfunctions
2025-08-14 10:21:21 -04:00
Mayukha Vadari
5dc0cee28a cache data instead of setting it in updateData (#5642)
Co-authored-by: Oleksandr <115580134+oleks-rip@users.noreply.github.com>
2025-08-11 15:52:21 -04:00
Mayukha Vadari
c15947da56 fix CI 2025-08-06 12:41:12 -04:00
Mayukha Vadari
9bc04244e7 Merge remote-tracking branch 'upstream/ripple/smart-escrow' into develop5 2025-08-06 12:36:25 -04:00
Mayukha Vadari
38c7a27010 clean up WASM functions a bit (#5628) 2025-08-05 18:06:34 -04:00
Mayukha Vadari
58741d2791 feat: return an int instead of boolean from finish, display in metadata (#5641)
* create STInt32 and STInt64, use it for sfWasmReturnCode in metadata

* get it actually working

* add tests

* update comment

* change type

* respond to comments
2025-08-04 09:25:47 -04:00
Mayukha Vadari
8426470506 feat: add other misc host functions (#5574) 2025-07-31 18:39:21 -04:00
Olek
ccc3280b1a Update wamr to 2.4.1 (#5640) 2025-07-31 13:42:53 -04:00
Mayukha Vadari
2847075705 fix: ensure GasUsed shows up in the metadata even on tecWASM_REJECTED (#5633)
* always set gas used

* fix

* add tests

* clean up
2025-07-31 10:57:56 -04:00
Olek
3108ca0549 Float point HF (#5611)
- added support for 8-byte  float point
2025-07-30 14:38:03 +00:00
Mayukha Vadari
3b849ff497 Add unit tests for host functions (#5578) 2025-07-29 17:54:48 -04:00
Mayukha Vadari
66776b6a85 test: codecov for WasmHostFuncWrapper.cpp (#5601) 2025-07-29 16:06:21 -04:00
Mayukha Vadari
c8c241b50d Merge remote-tracking branch 'upstream/develop' into develop4.6 2025-07-29 11:34:44 -04:00
Bronek Kozicki
44cb588371 Build options cleanup (#5581)
As we no longer support old compiler versions, we are bringing back some warnings by removing no longer relevant `-Wno-...` options.
2025-07-28 13:02:41 -04:00
Oleksandr
6f91b8f8d1 Fix windows 2025-07-28 12:29:39 -04:00
Mayukha Vadari
3d93379132 add header 2025-07-25 15:10:47 -04:00
Mayukha Vadari
84fd7d0126 Merge remote-tracking branch 'upstream/develop' into develop4.6 2025-07-25 14:56:36 -04:00
Mayukha Vadari
7f52287aae rename variables (#5609)
* rename variables

* instanceWrapper -> runtime

* size -> srcSize

* begin -> ptr
2025-07-25 11:54:31 -04:00
Mayukha Vadari
98b8986868 fix merge issues (mostly with Conan2 upgrade) 2025-07-23 15:52:03 -04:00
Mayukha Vadari
250f2842ee Merge remote-tracking branch 'upstream/develop' into develop4.5 2025-07-23 13:43:47 -04:00
Olek
9eca1a3a0c MPT and IOU support for amount and issue (#5573)
* MPT and IOU support for ammount and issue

* Fix tests
Update wasm code to the latest version
Remove deprecated tests
Remove deprecated wasm
2025-07-22 17:43:21 +00:00
Mayukha Vadari
24b7a03224 feat: add more keylet host functions (#5522) 2025-07-17 12:37:41 -04:00
Mayukha Vadari
9007097d24 Simplify host function boilerplate (#5534)
* enum for HF errors

* switch getData functions to be templates

* getData<SField> working

* Slice -> Bytes in host functions

* RET -> helper function instead of macro

* get template function working

* more organization/cleanup

* fix failures

* more cleanup

* Bytes -> Slice

* SFieldParam macro -> type alias

* fix return type

* fix bugs

* replace std::make_index_sequence

* remove `failed` from output

* remove complex function

* more uniformity

* respond to comments

* enum class HostFunctionError

* rename variable

* respond to comments

* remove templating

* [WIP] basic getData tests

* weird linker error

* fix issue
2025-07-15 04:28:59 +05:30
Olek
bc445ec6a2 Add hostfunctions schedule table
Remove opcode schedule table from wamr
2025-07-11 18:08:36 -04:00
Mayukha Vadari
4fa0ae521e disallow a computation allowance of 0 (#5541) 2025-07-09 00:34:17 +05:30
Olek
7bdf5fa8b8 Fix build.md wamr version (#5535) 2025-07-04 00:48:03 +05:30
Olek
65b0b976d9 Sync error codes (#5527)
* Sync error codes
2025-07-02 17:33:39 -04:00
Elliot.
a0d275feec chore: Clear CODEOWNERS (#5528) 2025-07-02 10:39:57 -07:00
Mayukha Vadari
ece3a8d7be Merge branch 'develop' into develop4 2025-06-30 21:33:30 +05:30
Olek
463acf51b5 preflight checks for wasm (#5517) 2025-06-30 09:34:38 -04:00
Olek
1cd16fab87 Host-functions perf test fixes (#5514) 2025-06-27 09:59:28 -04:00
Olek
add55c4f33 Host functions gas cost for wasm_runtime interface (#5500) 2025-06-25 14:04:04 +00:00
Olek
51a9c0ff59 Host function gas cost (#5488)
* Update Wamr to 2.3.1
* Add gas cost per host-function
* Fix windows build
* Fix wasm test
* Add no import test
2025-06-12 15:54:49 -04:00
Mayukha Vadari
6e8a5f0f4e fix: make host function traces easier to use, fix get_NFT bug (#5466)
Co-authored-by: Olek <115580134+oleks-rip@users.noreply.github.com>
2025-06-05 14:24:13 -04:00
Mayukha Vadari
8a33702f26 fix merge issues 2025-06-05 12:38:26 -04:00
Mayukha Vadari
a072d49802 Merge remote-tracking branch 'upstream/ripple/smart-escrow' into develop3.5 2025-06-05 11:51:53 -04:00
Mayukha Vadari
a0aeeb8e07 Merge remote-tracking branch 'upstream/develop' into develop3.5 2025-06-05 11:50:38 -04:00
Olek
383b225690 Fix processing nonexistent field (#5467) 2025-06-04 17:32:11 -04:00
Mayukha Vadari
ace2247800 Merge remote-tracking branch 'upstream/ripple/smart-escrow' into develop3.5 2025-06-04 14:15:17 -04:00
Olek
6a6fed5dce More hostfunctions (#5451)
* Bug fixes:
- Fix bugs found during schedule table tests
- Add more tests
- Add parameters passing for runEscrowWasm function

* Add new host-functions
 fix wamr logging
 add runtime passing through HF
 fix runEscrowWasm interface

* Improve logs

* Fix logging bug

* Set 4k limit for update_data HF

* allHF wasm module fixes
2025-05-30 19:01:27 -04:00
Mayukha Vadari
1f8aece8cd feat: add a GasUsed parameter to the metadata (#5456) 2025-05-29 16:36:55 -04:00
Mayukha Vadari
6c6f8cd4f9 Merge remote-tracking branch 'upstream/develop' into develop3 2025-05-29 13:05:11 -04:00
Mayukha Vadari
fb1311e013 uncomment???? 2025-05-28 14:00:50 -04:00
Mayukha Vadari
ce31acf030 debug comments 2025-05-28 13:48:38 -04:00
Mayukha Vadari
31ad5ac63b Merge remote-tracking branch 'upstream/ripple/smart-escrow' into develop3 2025-05-27 18:29:41 -04:00
Mayukha Vadari
1ede0bdec4 fix: fix fixtures (#5445) 2025-05-23 17:37:14 -04:00
Mayukha Vadari
aef32ead2c better WASM logging to match rippled (#5395)
* basic logging

* pass in Journal

* log level based on journal level

* clean up

* attempt at adding WAMR logging properly

* improve logline

* maybe_unused

* fix

* fix

* fix segfault

* add test
2025-05-23 10:31:02 -04:00
Mayukha Vadari
5b43ec7f73 refactor: switch function name from ready to finish (#5430) 2025-05-20 16:12:19 -04:00
Olek
1e9ff88a00 Fix CI build issues
* Mac build fix
* Windows build fix
* Windows instruction counter fix
2025-05-08 12:39:37 -04:00
Mayukha Vadari
bb9bb5f5c5 Merge branch 'ripple/smart-escrow' into develop2 2025-05-01 18:44:06 -04:00
Mayukha Vadari
c533abd8b6 Update size and compute cap defaults (#5417) 2025-05-01 18:41:51 -04:00
Olek
bb9bc764bc Switch to WAMR (#5416)
* Switch to WAMR
2025-05-01 18:02:06 -04:00
Mayukha Vadari
b4b53a6cb7 Merge branch 'ripple/smart-escrow' into develop2 2025-04-29 15:25:54 -04:00
Mayukha Vadari
9c0204906c fix reference fee tests 2025-04-29 15:25:00 -04:00
Mayukha Vadari
4670b373c1 try to fix tests 2025-04-29 14:10:27 -04:00
Mayukha Vadari
f03b5883bd More host functions (#5411)
* getNFT

* escrow keylet

* account keylet

* credential keylet

* oracle keylet

* hook everything in

* fix stuff
2025-04-29 12:39:12 -04:00
Mayukha Vadari
f8b2fe4dd5 fix imports 2025-04-28 17:43:15 -04:00
Mayukha Vadari
be4a0c9c2b Merge remote-tracking branch 'upstream/ripple/smart-escrow' into develop2 2025-04-28 17:14:28 -04:00
Mayukha Vadari
f37d52d8e9 Set up fees for WASM processing (#5393)
* set up fields

* throw error if allowance is too high

* votable gas price

* fix comments

* hook everything together

* make test less flaky (hopefully)

* fix other tests

* fix some tests

* fix tests

* clean up

* add more tests

* uncomment other tests

* respond to comments

* fix build

* respond to comments
2025-04-24 08:47:13 -04:00
Mayukha Vadari
177cdaf550 Connect votable gas limit into VM (#5360)
* [WIP] add gas limit

* [WIP] host function escrow tests

* finish test

* uncomment out tests
2025-03-25 10:55:33 -04:00
pwang200
1573a443b7 smart escrow devnet 1 host functions (#5353)
* devnet 1 host functions

* clang-format

* fix build issues
2025-03-24 17:07:17 -04:00
Mayukha Vadari
911c0466c0 Merge develop into ripple/smart-escrow (#5357)
* Set version to 2.4.0

* refactor: Remove unused and add missing includes (#5293)

The codebase is filled with includes that are unused, and which thus can be removed. At the same time, the files often do not include all headers that contain the definitions used in those files. This change uses clang-format and clang-tidy to clean up the includes, with minor manual intervention to ensure the code compiles on all platforms.

* refactor: Calculate numFeatures automatically (#5324)

Requiring manual updates of numFeatures is an annoying manual process that is easily forgotten, and leads to frequent merge conflicts. This change takes advantage of the `XRPL_FEATURE` and `XRPL_FIX` macros, and adds a new `XRPL_RETIRE` macro to automatically set `numFeatures`.

* refactor: Improve ordering of headers with clang-format (#5343)

Removes all manual header groupings from source and header files by leveraging clang-format options.

* Rename "deadlock" to "stall" in `LoadManager` (#5341)

What the LoadManager class does is stall detection, which is not the same as deadlock detection. In the condition of severe CPU starvation, LoadManager will currently intentionally crash rippled reporting `LogicError: Deadlock detected`. This error message is misleading as the condition being detected is not a deadlock. This change fixes and refactors the code in response.

* Adds hub.xrpl-commons.org as a new Bootstrap Cluster (#5263)

* fix: Error message for ledger_entry rpc (#5344)

Changes the error to `malformedAddress` for `permissioned_domain` in the `ledger_entry` rpc, when the account is not a string. This change makes it more clear to a user what is wrong with their request.

* fix: Handle invalid marker parameter in grpc call (#5317)

The `end_marker` is used to limit the range of ledger entries to fetch. If `end_marker` is less than `marker`, a crash can occur. This change adds an additional check.

* fix: trust line RPC no ripple flag (#5345)

The Trustline RPC `no_ripple` flag gets set depending on `lsfDefaultRipple` flag, which is not a flag of a trustline but of the account root. The `lsfDefaultRipple` flag does not provide any insight if this particular trust line has `lsfLowNoRipple` or `lsfHighNoRipple` flag set, so it should not be used here at all. This change simplifies the logic.

* refactor: Updates Conan dependencies: RocksDB (#5335)

Updates RocksDB to version 9.7.3, the latest version supported in Conan 1.x. A patch for 9.7.4 that fixes a memory leak is included.

* fix: Remove null pointer deref, just do abort (#5338)

This change removes the existing undefined behavior from `LogicError`, so we can be certain that there will be always a stacktrace.

De-referencing a null pointer is an old trick to generate `SIGSEGV`, which would typically also create a stacktrace. However it is also an undefined behaviour and compilers can do something else. A more robust way to create a stacktrace while crashing the program is to use `std::abort`, which we have also used in this location for a long time. If we combine the two, we might not get the expected behaviour - namely, the nullpointer deref followed by `std::abort`, as handled in certain compiler versions may not immediately cause a crash. We have observed stacktrace being wiped instead, and thread put in indeterminate state, then stacktrace created without any useful information.

* chore: Add PR number to payload (#5310)

This PR adds one more payload field to the libXRPL compatibility check workflow - the PR number itself.

* chore: Update link to ripple-binary-codec (#5355)

The link to ripple-binary-codec's definitions.json appears to be outdated. The updated link is also documented here: https://xrpl.org/docs/references/protocol/binary-format#definitions-file

* Prevent consensus from getting stuck in the establish phase (#5277)

- Detects if the consensus process is "stalled". If it is, then we can declare a 
  consensus and end successfully even if we do not have 80% agreement on
  our proposal.
  - "Stalled" is defined as:
    - We have a close time consensus
    - Each disputed transaction is individually stalled:
      - It has been in the final "stuck" 95% requirement for at least 2
        (avMIN_ROUNDS) "inner rounds" of phaseEstablish,
      - and either all of the other trusted proposers or this validator, if proposing,
        have had the same vote(s) for at least 4 (avSTALLED_ROUNDS) "inner
        rounds", and at least 80% of the validators (including this one, if
        appropriate) agree about the vote (whether yes or no).
- If we have been in the establish phase for more than 10x the previous
  consensus establish phase's time, then consensus is considered "expired",
  and we will leave the round, which sends a partial validation (indicating
  that the node is moving on without validating). Two restrictions avoid
  prematurely exiting, or having an extended exit in extreme situations.
  - The 10x time is clamped to be within a range of 15s
    (ledgerMAX_CONSENSUS) to 120s (ledgerABANDON_CONSENSUS).
  - If consensus has not had an opportunity to walk through all avalanche
    states (defined as not going through 8 "inner rounds" of phaseEstablish),
    then ConsensusState::Expired is treated as ConsensusState::No.
- When enough nodes leave the round, any remaining nodes will see they've
  fallen behind, and move on, too, generally before hitting the timeout. Any
  validations or partial validations sent during this time will help the
  consensus process bring the nodes back together.

---------

Co-authored-by: Michael Legleux <mlegleux@ripple.com>
Co-authored-by: Bart <bthomee@users.noreply.github.com>
Co-authored-by: Ed Hennis <ed@ripple.com>
Co-authored-by: Bronek Kozicki <brok@incorrekt.com>
Co-authored-by: Darius Tumas <Tokeiito@users.noreply.github.com>
Co-authored-by: Sergey Kuznetsov <skuznetsov@ripple.com>
Co-authored-by: cyan317 <120398799+cindyyan317@users.noreply.github.com>
Co-authored-by: Vlad <129996061+vvysokikh1@users.noreply.github.com>
Co-authored-by: Alex Kremer <akremer@ripple.com>
2025-03-20 16:47:14 -04:00
Mayukha Vadari
b6a95f9970 PoC Smart Escrows (#5340)
* wasmedge in unittest

* add WashVM.h and cpp

* accountID comparison (vector<u8>) working

* json decode tx and ledger object with two buffers working

* wasm return a buffer working

* add a failure test case to P2P3

* host function return ledger sqn

* instruction gas and host function gas

* basics

* add scaffold

* add amendment check

* working PoC

* get test working

* fix clang-format

* prototype #2

* p2p3

* [WIP] P4

* P5

* add calculateBaseFee

* add FinishFunction preflight checks (+ tests)

* additional reserve for sfFinishFunction

* higher fees for EscrowFinish

* rename amendment to SmartEscrow

* make fee voting changes, add basic tests

* clean up

* clean up

* clean up

* more cleanup

* add subscribe tests

* add more tests

* undo formatting

* undo formatting

* remove bad comment

* more debugging statements

* fix clang-format

* fix rebase issues

* fix more rebase issues

* more rebase fixes

* add source code for wasm

* respond to comments

* add const

---------

Co-authored-by: Peng Wang <pwang200@gmail.com>
2025-03-20 14:08:06 -04:00
211 changed files with 33282 additions and 1377 deletions

View File

@@ -7,6 +7,8 @@ ignorePaths:
- cmake/**
- LICENSE.md
- .clang-tidy
- src/test/app/wasm_fixtures/**/*.wat
- src/test/app/wasm_fixtures/*.c
language: en
allowCompoundWords: true # TODO (#6334)
ignoreRandomStrings: true
@@ -65,6 +67,7 @@ words:
- Btrfs
- Buildx
- canonicality
- cdylib
- canonicalised
- changespq
- checkme
@@ -216,6 +219,7 @@ words:
- Nyffenegger
- onlatest
- ostr
- oxalica
- pargs
- partitioner
- paychan
@@ -262,6 +266,8 @@ words:
- Rohrs
- roundings
- rustc
- rustfmt
- rustup
- sahyadri
- Satoshi
- scons
@@ -290,6 +296,7 @@ words:
- STATSDCOLLECTOR
- stissue
- stnum
- stnumber
- stobj
- stobject
- stpath
@@ -305,6 +312,7 @@ words:
- takerpays
- ters
- TMEndpointv2
- toolchain
- tparam
- trixie
- tx
@@ -337,6 +345,7 @@ words:
- unsquelch
- unsquelched
- unsquelching
- unsuffixed
- unvalidated
- unveto
- unvetoed
@@ -354,6 +363,7 @@ words:
- wthread
- xbridge
- xchain
- xfloat
- ximinez
- XMACRO
- xrpkuwait

View File

@@ -11,6 +11,9 @@ endfunction()
function(create_symbolic_link target link)
endfunction()
function(xrpl_add_benchmark name)
endfunction()
macro(exclude_from_default target_)
endmacro()

View File

@@ -1,10 +1,10 @@
<!--
This PR template helps you to write a good pull request description.
This PR template helps you write a good pull request description.
Please feel free to include additional useful information even beyond what is requested below.
If your branch is on a personal fork and has a name that allows it to
run CI build/test jobs (e.g. "ci/foo"), remember to rename it BEFORE
opening the PR. This avoids unnecessary redundant test runs. Renaming
opening the PR. This avoids redundant test runs. Renaming
the branch after opening the PR will close the PR.
https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-branches-in-your-repository/renaming-a-branch
-->
@@ -15,7 +15,7 @@ https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-
Please include a summary of the changes.
This may be a direct input to the release notes.
If too broad, please consider splitting into multiple PRs.
If a relevant task or issue, please link it here.
If there is a relevant task or issue, please link it here.
-->
### Context of Change
@@ -65,5 +65,5 @@ This section may not be needed if your change includes thoroughly commented unit
<!--
## Future Tasks
For future tasks related to PR.
For future tasks related to this PR.
-->

View File

@@ -1,3 +1,6 @@
benchmarks.libxrpl > xrpl.basics
benchmarks.libxrpl > xrpl.config
benchmarks.libxrpl > xrpl.nodestore
libxrpl.basics > xrpl.basics
libxrpl.conditions > xrpl.basics
libxrpl.conditions > xrpl.conditions
@@ -204,6 +207,7 @@ xrpl.conditions > xrpl.basics
xrpl.conditions > xrpl.protocol
xrpl.config > xrpl.basics
xrpl.core > xrpl.basics
xrpl.core > xrpl.config
xrpl.core > xrpl.json
xrpl.core > xrpl.protocol
xrpl.json > xrpl.basics
@@ -307,6 +311,7 @@ xrpld.rpc > xrpl.server
xrpld.rpc > xrpl.shamap
xrpld.rpc > xrpl.tx
xrpld.shamap > xrpl.basics
xrpld.shamap > xrpl.core
xrpld.shamap > xrpld.core
xrpld.shamap > xrpl.nodestore
xrpld.shamap > xrpl.protocol

View File

@@ -1,5 +1,5 @@
{
"image_tag": "sha-e29b523",
"image_tag": "sha-3122de8",
"configs": {
"ubuntu": [
{

View File

@@ -1,4 +1,4 @@
name: Build Nix Docker images
name: Build `nix` Docker images
on:
push:
@@ -8,6 +8,7 @@ on:
- ".github/workflows/build-nix-images.yml"
- "flake.nix"
- "flake.lock"
- "rust-toolchain.toml"
- "nix/**"
- "!nix/docker/README.md"
- "!nix/devshell.nix"
@@ -18,6 +19,7 @@ on:
- ".github/workflows/build-nix-images.yml"
- "flake.nix"
- "flake.lock"
- "rust-toolchain.toml"
- "nix/**"
- "!nix/docker/README.md"
- "!nix/devshell.nix"
@@ -36,7 +38,7 @@ defaults:
jobs:
build-merge:
name: Build and push nix-${{ matrix.distro.name }}
name: Build and push `nix-${{ matrix.distro.name }}` image
permissions:
contents: read
packages: write
@@ -54,7 +56,7 @@ jobs:
base_image: debian:bookworm
- name: rhel
base_image: registry.access.redhat.com/ubi9/ubi:latest
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32
with:
image_name: xrpld/nix-${{ matrix.distro.name }}
dockerfile: nix/docker/Dockerfile

View File

@@ -1,4 +1,4 @@
name: Build packaging Docker images
name: Build `packaging` Docker images
on:
push:
@@ -26,7 +26,7 @@ defaults:
jobs:
build-merge:
name: Build and push packaging-${{ matrix.distro.name }}
name: Build and push `packaging-${{ matrix.distro.name }}` image
permissions:
contents: read
packages: write
@@ -38,7 +38,7 @@ jobs:
base_image: debian:bookworm
- name: rhel
base_image: registry.access.redhat.com/ubi9/ubi:latest
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32
with:
image_name: xrpld/packaging-${{ matrix.distro.name }}
dockerfile: package/Dockerfile

View File

@@ -0,0 +1,38 @@
name: Build `pre-commit` Docker image
on:
push:
branches:
- develop
paths:
- ".github/workflows/build-pre-commit-image.yml"
- "bin/pre-commit/Dockerfile"
- "rust-toolchain.toml"
pull_request:
paths:
- ".github/workflows/build-pre-commit-image.yml"
- "bin/pre-commit/Dockerfile"
- "rust-toolchain.toml"
workflow_dispatch:
concurrency:
# Read `on-trigger.yml` for the rationale behind this concurrency group name.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
build-merge:
name: Build and push `pre-commit` image
permissions:
contents: read
packages: write
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32
with:
image_name: xrpld/pre-commit
dockerfile: bin/pre-commit/Dockerfile
base_image: ubuntu:26.04
push: ${{ github.event_name == 'push' }}

View File

@@ -20,4 +20,4 @@ on:
jobs:
check_title:
if: ${{ github.event.pull_request.draft != true }}
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@d7c65e49225a38f6d8010eacf017bb5a98d7476c

View File

@@ -14,7 +14,7 @@ on:
jobs:
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
run-hooks:
uses: XRPLF/actions/.github/workflows/pre-commit.yml@1bde119a1ab71305ba5d3716e7a82cea1c7bdede
uses: XRPLF/actions/.github/workflows/pre-commit.yml@3ba08d6ddf114092891d48491fc2e26c3ba15552
with:
runs_on: ubuntu-latest
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'
container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-f56b79f" }'

View File

@@ -41,13 +41,13 @@ env:
jobs:
build:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-e29b523
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-3122de8
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
with:
enable_ccache: false

View File

@@ -113,7 +113,7 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
with:
enable_ccache: ${{ inputs.ccache_enabled }}
@@ -223,11 +223,13 @@ jobs:
BUILD_TYPE: ${{ inputs.build_type }}
CMAKE_TARGET: ${{ inputs.cmake_target }}
run: |
set -o pipefail
cmake \
--build . \
--config "${BUILD_TYPE}" \
--parallel "${BUILD_NPROC}" \
--target "${CMAKE_TARGET}"
--target "${CMAKE_TARGET}" \
2>&1 | tee "${GITHUB_WORKSPACE}/build.log"
- name: Show ccache statistics
if: ${{ inputs.ccache_enabled }}
@@ -264,7 +266,7 @@ jobs:
./xrpld --definitions | python3 -m json.tool >server_definitions.json
- name: Upload server definitions
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-gcc-release-amd64' }}
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'ubuntu-gcc-debug-amd64-coverage' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: server-definitions
@@ -322,27 +324,46 @@ jobs:
PRELOAD=""
fi
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee "${GITHUB_WORKSPACE}/unittest.log"
- name: Show test failure summary
if: ${{ failure() && !inputs.build_only }}
env:
WORKING_DIR: ${{ runner.os == 'Windows' && format('{0}\{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
# Smoke-run every benchmark module with a single repetition to confirm the
# benchmarks still build and execute. This is a correctness check, not a
# performance measurement, so it is skipped for instrumented builds
# (sanitizers/coverage/voidstar), where it would be slow and meaningless,
# and on Windows, where the `install` target does not build them.
- name: Run the benchmarks
if: ${{ !inputs.build_only && runner.os != 'Windows' && env.SANITIZERS_ENABLED == 'false' && env.COVERAGE_ENABLED != 'true' && env.VOIDSTAR_ENABLED != 'true' }}
working-directory: ${{ env.BUILD_DIR }}
run: |
if [ ! -d "${WORKING_DIR}" ]; then
echo "Working directory '${WORKING_DIR}' does not exist."
exit 0
fi
rc=0
while IFS= read -r bench; do
echo "::group::${bench}"
"./${bench}" --benchmark_repetitions=1 || rc=1
echo "::endgroup::"
done < <(find src/benchmarks -type f -perm -u+x -name 'xrpl.bench.*')
exit "${rc}"
cd "${WORKING_DIR}"
- name: Show build/test failure summary
if: ${{ failure() }}
run: |
cd "${GITHUB_WORKSPACE}"
if [ ! -f unittest.log ]; then
echo "unittest.log not found; embedded tests may not have run."
exit 0
fi
if ! grep -E "failed" unittest.log; then
echo "Log present but no failure lines found in unittest.log."
if [ -f unittest.log ]; then
if ! grep -E "failed" unittest.log | grep -vE "^I[0-9]|^[0-9]+> (ERR:|FTL:)"; then
echo "unittest.log present but no failure lines found."
fi
elif [ -f build.log ]; then
# GCC/Clang emit "error:" (covers "fatal error:"); MSVC emits
# "error C####:", "error LNK####:", and "fatal error LNK####:".
# -A6 prints the lines that follow each match (source line, caret,
# notes, and the "N errors generated" tally) to capture the whole
# diagnostic block.
if ! grep -E -A6 "error:|error C[0-9]{4}|error LNK[0-9]{4}|fatal error" build.log; then
echo "build.log present but no compile errors found."
fi
else
echo "unittest.log/build.log not found; something went wrong."
exit 1
fi
- name: Debug failure (Linux)
if: ${{ failure() && runner.os == 'Linux' && !inputs.build_only }}

View File

@@ -34,7 +34,7 @@ jobs:
needs: [determine-files]
if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }}
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-e29b523"
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-3122de8"
permissions:
contents: read
issues: write
@@ -43,7 +43,7 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
with:
enable_ccache: false

View File

@@ -30,7 +30,7 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"

View File

@@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"

View File

@@ -40,7 +40,7 @@ defaults:
jobs:
upload:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-e29b523
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-3122de8
env:
REMOTE_NAME: ${{ inputs.remote_name }}
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}

View File

@@ -68,7 +68,7 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
with:
enable_ccache: false

3
.gitignore vendored
View File

@@ -81,6 +81,9 @@ DerivedData
# Python
__pycache__
# Rust build artifacts.
target/
# Direnv's directory
/.direnv

View File

@@ -55,7 +55,7 @@ repos:
types_or: [c++, c]
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: dd18dad857d6133e90bbe478f4f2f22ec0030269 # frozen: v22.1.5
rev: f4d7745e17a28aad7eed2f4874ca8d1568c11c4c # frozen: v22.1.8
hooks:
- id: clang-format
args: [--style=file]
@@ -68,7 +68,7 @@ repos:
- id: gersemi
- repo: https://github.com/rbubley/mirrors-prettier
rev: 39e2973981e6d2f9b6c543b0086a2d2393abdc89 # frozen: v3.9.4
rev: 9337a74165b178ae2c766f60bee7252a0f06f3e8 # frozen: v3.9.5
hooks:
- id: prettier
args: [--end-of-line=auto]

View File

@@ -96,6 +96,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(
@@ -131,6 +132,10 @@ else()
endif()
target_link_libraries(xrpl_libs INTERFACE ${nudb})
if(benchmark)
find_package(benchmark REQUIRED)
endif()
if(coverage)
include(XrplCov)
endif()
@@ -145,3 +150,7 @@ if(tests)
include(CTest)
add_subdirectory(src/tests/libxrpl)
endif()
if(benchmark)
add_subdirectory(src/benchmarks/libxrpl)
endif()

4
CODEOWNERS Normal file
View File

@@ -0,0 +1,4 @@
# By default, anyone can review changes.
# The CI tooling team should review changes to the CI configuration.
/.github/ @XRPLF/ci-tooling

View File

@@ -83,6 +83,7 @@ If you create new source files, they must be organized as follows:
`src/libxrpl`.
- All other non-test files must go under `src/xrpld`.
- All test source files must go under `src/test`.
- All benchmark source files must go under `src/benchmarks`.
The source must be formatted according to the style guide below. The easiest
way to satisfy this is to install the [`pre-commit`](#pre-commit-hooks) hooks,

55
bin/pre-commit/Dockerfile Normal file
View File

@@ -0,0 +1,55 @@
ARG BASE_IMAGE=ubuntu:26.04
FROM ${BASE_IMAGE}
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
ENTRYPOINT ["/bin/bash"]
ARG DEBIAN_FRONTEND=noninteractive
RUN <<EOF
pkgs=()
pkgs+=(curl) # Required to install nix.
pkgs+=(doxygen) # Needed for Clio's check-doxygen-docs.sh.
pkgs+=(git) # Required for prepare-runner.
pkgs+=(libatomic1) # Required to run pre-commit provided `node`.
pkgs+=(python3) # Python 3 interpreter.
pkgs+=(python3-pip) # Package manager for Python applications.
pkgs+=(xz-utils) # Required to install nix
apt-get update
apt-get install -y --no-install-recommends "${pkgs[@]}"
apt-get clean
rm -rf /var/lib/apt/lists/*
EOF
ARG PRE_COMMIT_VERSION=4.6.0
RUN pip install --no-cache --break-system-packages \
pre-commit==${PRE_COMMIT_VERSION}
RUN sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon --yes
# Add nix to PATH and set NIX environment variables,
# so nix is available in all shells including non-interactive shells (e.g., GitHub Actions).
ENV PATH="/nix/var/nix/profiles/default/bin:${PATH}"
ENV NIX_PROFILES="/nix/var/nix/profiles/default"
ENV NIX_SSL_CERT_FILE="/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"
# Verify nix installation
RUN nix --version
ENV RUSTUP_HOME="/opt/rust/rustup"
ENV CARGO_HOME="/opt/rust/cargo"
ENV PATH="/opt/rust/cargo/bin:${PATH}"
WORKDIR /tmp
COPY rust-toolchain.toml /tmp/rust-toolchain.toml
RUN <<EOF
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --no-modify-path --profile minimal --default-toolchain none
rustup toolchain install
rustup show
cargo fmt --version
EOF
WORKDIR /

View File

@@ -91,14 +91,19 @@ def main():
if not os.environ.get("TIDY"):
return 0
repo_root = Path(
subprocess.check_output(
["git", "rev-parse", "--show-toplevel"],
cwd=Path(__file__).parent,
text=True,
).strip()
# Derive the repo root from git so this keeps working regardless of where
# under the tree this script lives. Fall back to the script location
# (bin/pre-commit/ is two levels below the repo root) if git is unavailable.
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
cwd=Path(__file__).parent,
)
if result.returncode == 0:
repo_root = Path(result.stdout.strip())
else:
repo_root = Path(__file__).parent.parent.parent
files = staged_files(repo_root)
if not files:
return 0

View File

@@ -1298,6 +1298,39 @@
# Example:
# owner_reserve = 200000 # 0.2 XRP
#
# gas_limit = <gas>
#
# The gas limit is the maximum amount of gas that can be
# consumed by a single transaction. The gas limit is used to prevent
# transactions from consuming too many resources.
#
# If this parameter is unspecified, xrpld will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# gas_limit = 1000000 # 1 million gas
#
# bytecode_size_limit = <bytes>
#
# The bytecode size limit is the maximum size of a WASM extension in
# bytes. The size limit is used to prevent extensions from consuming
# too many resources.
#
# If this parameter is unspecified, xrpld will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# bytecode_size_limit = 100000 # 100 kb
#
# gas_price = <micro-drops>
#
# The gas price is the conversion between WASM gas and its price in drops.
#
# If this parameter is unspecified, xrpld will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# gas_price = 1000000 # 1 drop per gas
#-------------------------------------------------------------------------------
#
# 9. Misc Settings

View File

@@ -0,0 +1,36 @@
include(isolate_headers)
# Define a benchmark executable for the module `name`.
#
# This follows the same general pattern as other build helpers in this repo
# (e.g. `add_module`): create a target and isolate headers, but here the target
# is a benchmark executable and no `add_test(...)` is registered.
#
# `isolate_headers` exposes only `${CMAKE_CURRENT_SOURCE_DIR}/${name}` on the
# include path, rooted at `src`, so a benchmark's own headers are reached as
# `<benchmarks/.../${name}/...>` and nothing else in the tree leaks in.
function(xrpl_add_benchmark name)
set(target ${PROJECT_NAME}.bench.${name})
file(
GLOB_RECURSE sources
CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp"
)
add_executable(${target} ${ARGN} ${sources})
# Benchmark sources register cases through Google Benchmark's static
# registrars (anonymous-namespace lambdas). Merging several such files into
# one unity translation unit collides those internal-linkage entities, so
# keep benchmarks out of the unity build - mirroring xrpl.libpb in
# XrplCore.cmake. Each file compiles fine on its own.
set_target_properties(${target} PROPERTIES UNITY_BUILD OFF)
isolate_headers(
${target}
"${CMAKE_SOURCE_DIR}/src"
"${CMAKE_CURRENT_SOURCE_DIR}/${name}"
PRIVATE
)
endfunction()

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>

View File

@@ -30,6 +30,8 @@ if(tests)
endif()
endif()
option(benchmark "Build benchmarks" ON)
# Enabled by default so every header is compiled on its own as the main file of
# its own compile_commands.json entry - this is what lets clang-tidy (and clangd
# and IDEs) analyse a header's own includes directly. The per-header objects are

View File

@@ -3,6 +3,7 @@
"requires": [
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688",
"wasmi/1.0.9#1fecdab9b90c96698eb35ea99ca4f5cb%1782307153.343419",
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447",
"soci/4.0.3#e726491a03468795453f7c83fc924a96%1782392402.679521",
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1782307151.633168",
@@ -25,6 +26,7 @@
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1782392402.681654",
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1782392402.296732",
"boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1782392419.475605",
"benchmark/1.9.5#b885dc73ad67b40a55d45684d1c88ad1%1782736613.864841",
"abseil/20250127.0#9ef01c1451a8340f9022e46238c0fbb6%1783945159.651047"
],
"build_requires": [

View File

@@ -20,6 +20,22 @@ compiler.libcxx={{ detect_api.detect_libcxx(compiler, version, compiler_exe) }}
{% endif %}
[conf]
{# The Boost recipe builds with b2, which doesn't use Conan's toolchain files. #}
{# Instead it hand-rolls the compiler for user-config.jam, #}
{# and its fallback probes a version-suffixed binary (e.g. `g++-15`) before plain `g++`. #}
{# Inside the Nix shell the wrapper only provides `g++`/`gcc` (no `-15` suffix), #}
{# so on a host that also has a system `g++-15` the probe escapes Nix #}
{# and picks the system compiler, which is mismatched with the Nix libraries #}
{# and breaks the build (e.g. Boost.Stacktrace link checks fail). #}
{# Pinning the executables here short-circuits that probe so Boost (and the rest of the toolchain) #}
{# resolve the same compiler. #}
{# Not part of the package ID, so binaries stay shareable. #}
{% if os != "Windows" %}
{% set cc_exe = {"gcc": "gcc", "clang": "clang", "apple-clang": "clang"}.get(compiler) %}
{% set cxx_exe = {"gcc": "g++", "clang": "clang++", "apple-clang": "clang++"}.get(compiler) %}
tools.build:compiler_executables={'c':'{{ cc_exe }}','cpp':'{{ cxx_exe }}'}
{% endif %}
{# By default, Conan tries to reuse binaries built with different cppstd versions. #}
{# We want to avoid that to improve reproduceability, so we add the cppstd version to the package ID. #}
{# More info: https://docs.conan.io/2/reference/extensions/binary_compatibility.html #}

View File

@@ -15,6 +15,7 @@ class Xrpl(ConanFile):
settings = "os", "compiler", "build_type", "arch"
options = {
"assertions": [True, False],
"benchmark": [True, False],
"coverage": [True, False],
"fPIC": [True, False],
"jemalloc": [True, False],
@@ -33,6 +34,7 @@ class Xrpl(ConanFile):
"nudb/2.0.9",
"openssl/3.6.3",
"soci/4.0.3",
"wasmi/1.0.9",
"zlib/1.3.2",
]
@@ -46,6 +48,7 @@ class Xrpl(ConanFile):
default_options = {
"assertions": False,
"benchmark": True,
"coverage": False,
"fPIC": True,
"jemalloc": False,
@@ -129,6 +132,8 @@ class Xrpl(ConanFile):
self.options["boost"].without_cobalt = True
def requirements(self):
if self.options.benchmark:
self.requires("benchmark/1.9.5")
self.requires("boost/1.91.0", force=True, transitive_headers=True)
self.requires("date/3.0.4", transitive_headers=True)
if self.options.jemalloc:
@@ -162,6 +167,7 @@ class Xrpl(ConanFile):
def generate(self):
tc = CMakeToolchain(self)
tc.variables["tests"] = self.options.tests
tc.variables["benchmark"] = self.options.benchmark
tc.variables["assert"] = self.options.assertions
tc.variables["coverage"] = self.options.coverage
tc.variables["jemalloc"] = self.options.jemalloc
@@ -216,6 +222,7 @@ class Xrpl(ConanFile):
"soci::soci",
"secp256k1::secp256k1",
"sqlite3::sqlite",
"wasmi::wasmi",
"xxhash::xxhash",
"zlib::zlib",
]

23
flake.lock generated
View File

@@ -36,7 +36,28 @@
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"nixpkgs-custom-glibc": "nixpkgs-custom-glibc"
"nixpkgs-custom-glibc": "nixpkgs-custom-glibc",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1784611586,
"narHash": "sha256-OfqgY+0hp/zseZB7uyH0U8kIDPS4scZZCyAurEplvG0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "14f58845249f3552a89b07772626b8d3c632fa86",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
}
},

View File

@@ -10,12 +10,25 @@
url = "github:NixOS/nixpkgs/9cd98386a38891d1074fc18036b842dc4416f562";
flake = false;
};
# Pinned Rust toolchains, delivered from the Nix store. Lets the Nix CI
# image and dev shell honour the single `rust-toolchain.toml` pin (shared
# with the rustup-based non-Nix runners) while staying hermetic — the
# toolchain lands in the image's Nix closure and is locked by flake.lock.
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{ nixpkgs, nixpkgs-custom-glibc, ... }:
{
nixpkgs,
nixpkgs-custom-glibc,
rust-overlay,
...
}:
let
forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-custom-glibc; };
forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-custom-glibc rust-overlay; };
in
{
devShells = forEachSystem (import ./nix/devshell.nix);

View File

@@ -549,8 +549,21 @@ public:
setround(RoundingMode inMode);
/**
* Returns which mantissa scale is currently in use for normalization.
* Convert an integer to a RoundingMode, validating that it is in range.
*
* Returns std::nullopt if the value does not correspond to a valid
* RoundingMode.
*/
static std::optional<RoundingMode>
checkedRoundingMode(int mode) noexcept
{
if (mode < static_cast<int>(RoundingMode::ToNearest) ||
mode > static_cast<int>(RoundingMode::Upward))
return std::nullopt;
return static_cast<RoundingMode>(mode);
}
/**
* If you think you need to call this outside of unit tests, no you don't.
*/
static MantissaRange::MantissaScale

View File

@@ -93,6 +93,7 @@ struct Keys
static constexpr auto kBbtOptions = "bbt_options";
static constexpr auto kBgThreads = "bg_threads";
static constexpr auto kBlockSize = "block_size";
static constexpr auto kBytecodeSizeLimit = "bytecode_size_limit";
static constexpr auto kCacheAge = "cache_age";
static constexpr auto kCacheMb = "cache_mb";
static constexpr auto kCacheSize = "cache_size";
@@ -107,6 +108,8 @@ struct Keys
static constexpr auto kFileSizeMult = "file_size_mult";
static constexpr auto kFilterBits = "filter_bits";
static constexpr auto kFilterFull = "filter_full";
static constexpr auto kGasLimit = "gas_limit";
static constexpr auto kGasPrice = "gas_price";
static constexpr auto kHardSet = "hard_set";
static constexpr auto kHighThreads = "high_threads";
static constexpr auto kHoldTime = "hold_time";

View File

@@ -6,6 +6,7 @@
#include <xrpl/basics/TaggedCache.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/Fees.h>
#include <boost/asio.hpp>
@@ -246,6 +247,9 @@ public:
virtual DatabaseCon&
getWalletDB() = 0;
[[nodiscard]] virtual Fees
getFees() const = 0;
// Temporary: Get the underlying Application for functions that haven't
// been migrated yet. This should be removed once all code is migrated.
virtual Application&

View File

@@ -13,6 +13,7 @@
#include <xrpl/protocol/TxMeta.h>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
@@ -68,6 +69,18 @@ public:
deliver_ = amount;
}
void
setGasUsed(std::optional<std::uint32_t> const gasUsed)
{
gasUsed_ = gasUsed;
}
void
setVMReturnCode(std::int32_t const vmReturnCode)
{
vmReturnCode_ = vmReturnCode;
}
/**
* Get the number of modified entries
*/
@@ -88,6 +101,8 @@ public:
private:
std::optional<STAmount> deliver_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> vmReturnCode_;
};
} // namespace xrpl

View File

@@ -16,6 +16,7 @@
#include <xrpl/protocol/XRPAmount.h>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <map>
#include <optional>
@@ -62,6 +63,8 @@ public:
TER ter,
std::optional<STAmount> const& deliver,
std::optional<uint256 const> const& parentBatchId,
std::optional<std::uint32_t> const& gasUsed,
std::optional<std::int32_t> const& vmReturnCode,
bool isDryRun,
beast::Journal j);

View File

@@ -23,6 +23,8 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
namespace xrpl {
template <ValidIssueType T>
@@ -255,4 +257,15 @@ escrowUnlockApplyHelper<MPTIssue>(
journal);
}
template <class T>
static int32_t
calculateAdditionalReserve(T const& finishFunction)
{
if (!finishFunction)
return 1;
// First 500 bytes included in the normal reserve
// Each additional 500 bytes requires an additional reserve
return 1 + (finishFunction->size() / 500);
}
} // namespace xrpl

View File

@@ -263,10 +263,11 @@ constructLoanState(
Number const& principalOutstanding,
Number const& managementFeeOutstanding);
// Constructs a valid LoanState object from a Loan object, which always has
// rounded values
// Overload of constructLoanState() that reads the three tracked fields
// directly from a Loan ledger object, which always holds rounded values,
// rather than taking them as separate Number arguments.
LoanState
constructRoundedLoanState(SLE::const_ref loan);
constructLoanState(SLE::const_ref loan);
Number
computeManagementFee(

View File

@@ -10,6 +10,15 @@ namespace xrpl {
// This was the reference fee units used in the old fee calculation.
inline constexpr std::uint32_t kFeeUnitsDeprecated = 10;
// Number of micro-drops in one drop.
constexpr std::uint32_t microDropsPerDrop{1'000'000};
/**
* Maximum Feature Extension fee settings.
*/
inline constexpr std::uint32_t kMaxGasLimit{2'000'000};
inline constexpr std::uint32_t kMaxBytecodeSizeLimit{200'000};
/**
* Reflects the fee settings for a particular ledger.
*
@@ -33,6 +42,21 @@ struct Fees
*/
XRPAmount increment{0};
/**
* @brief Gas limit for Feature Extensions (instructions).
*/
std::uint32_t gasLimit{0};
/**
* @brief Bytecode size limit for Feature Extensions (bytes).
*/
std::uint32_t bytecodeSizeLimit{0};
/**
* @brief Price of WASM gas (micro-drops).
*/
std::uint32_t gasPrice{0};
explicit Fees() = default;
Fees(Fees const&) = default;
Fees&

View File

@@ -241,6 +241,12 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept
Keylet
escrow(AccountID const& src, std::uint32_t seq) noexcept;
inline Keylet
escrow(uint256 const& key) noexcept
{
return {ltESCROW, key};
}
/**
* A PaymentChannel
*/

View File

@@ -322,6 +322,16 @@ constexpr std::uint8_t kVaultMaximumIouScale = 18;
*/
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.
*/

View File

@@ -129,8 +129,12 @@ enum TEMcodes : TERUnderlyingType {
temARRAY_TOO_LARGE,
temBAD_TRANSFER_FEE,
temINVALID_INNER_BATCH,
temBAD_MPT,
temBAD_CIPHERTEXT,
temBAD_WASM,
temINVALID_BYTECODE,
temTEMP_DISABLED,
};
//------------------------------------------------------------------------------
@@ -179,6 +183,8 @@ enum TEFcodes : TERUnderlyingType {
tefINVALID_LEDGER_FIX_TYPE,
tefNO_DST_PARTIAL,
tefBAD_PATH_COUNT,
tefNO_BYTECODE,
tefBYTECODE_NOT_INCLUDED,
};
//------------------------------------------------------------------------------
@@ -370,6 +376,8 @@ enum TECcodes : TERUnderlyingType {
tecNO_DELEGATE_PERMISSION = 198,
tecBAD_PROOF = 199,
tecNO_SPONSOR_PERMISSION = 200,
tecOUT_OF_GAS = 201,
tecBYTECODE_REJECTED = 202,
};
//------------------------------------------------------------------------------

View File

@@ -95,6 +95,12 @@ public:
if (obj.isFieldPresent(sfParentBatchID))
parentBatchID_ = obj.getFieldH256(sfParentBatchID);
if (obj.isFieldPresent(sfGasUsed))
gasUsed_ = obj.getFieldU32(sfGasUsed);
if (obj.isFieldPresent(sfVMReturnCode))
vmReturnCode_ = obj.getFieldI32(sfVMReturnCode);
}
[[nodiscard]] std::optional<STAmount> const&
@@ -115,6 +121,30 @@ public:
parentBatchID_ = id;
}
void
setGasUsed(std::optional<std::uint32_t> const gasUsed)
{
gasUsed_ = gasUsed;
}
[[nodiscard]] std::optional<std::uint32_t> const&
getGasUsed() const
{
return gasUsed_;
}
void
setVMReturnCode(std::optional<std::int32_t> const vmReturnCode)
{
vmReturnCode_ = vmReturnCode;
}
[[nodiscard]] std::optional<std::int32_t> const&
getVMReturnCode() const
{
return vmReturnCode_;
}
private:
uint256 transactionID_;
std::uint32_t ledgerSeq_;
@@ -123,6 +153,8 @@ private:
std::optional<STAmount> deliveredAmount_;
std::optional<uint256> parentBatchID_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> vmReturnCode_;
STArray nodes_;
};

View File

@@ -15,6 +15,7 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FEATURE(SmartEscrow, Supported::No, VoteBehavior::DefaultNo)
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocolV1_1, Supported::No, VoteBehavior::DefaultNo)

View File

@@ -309,6 +309,11 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
{sfBaseFeeDrops, SoeOptional},
{sfReserveBaseDrops, SoeOptional},
{sfReserveIncrementDrops, SoeOptional},
// Smart Escrow fields
{sfGasLimit, SoeOptional},
{sfBytecodeSizeLimit, SoeOptional},
{sfGasPrice, SoeOptional},
{sfPreviousTxnID, SoeOptional},
{sfPreviousTxnLgrSeq, SoeOptional},
}))
@@ -339,6 +344,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
{sfCondition, SoeOptional},
{sfCancelAfter, SoeOptional},
{sfFinishAfter, SoeOptional},
{sfBytecode, SoeOptional},
{sfData, SoeOptional},
{sfSourceTag, SoeOptional},
{sfDestinationTag, SoeOptional},
{sfOwnerNode, SoeRequired},

View File

@@ -119,6 +119,11 @@ TYPED_SFIELD(sfSponsoringOwnerCount, UINT32, 71)
TYPED_SFIELD(sfSponsoringAccountCount, UINT32, 72)
TYPED_SFIELD(sfRemainingOwnerCount, UINT32, 73)
TYPED_SFIELD(sfSponsorFlags, UINT32, 74)
TYPED_SFIELD(sfGasLimit, UINT32, 75)
TYPED_SFIELD(sfBytecodeSizeLimit, UINT32, 76)
TYPED_SFIELD(sfGasPrice, UINT32, 77)
TYPED_SFIELD(sfGas, UINT32, 78)
TYPED_SFIELD(sfGasUsed, UINT32, 79)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -236,8 +241,9 @@ TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15, SField::kSmdNeedsAsset
TYPED_SFIELD(sfPeriodicPayment, NUMBER, 16)
TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::kSmdNeedsAsset | SField::kSmdDefault)
// int32
// 32-bit signed (common)
TYPED_SFIELD(sfLoanScale, INT32, 1)
TYPED_SFIELD(sfVMReturnCode, INT32, 2)
// currency amount (common)
TYPED_SFIELD(sfAmount, AMOUNT, 1)
@@ -267,7 +273,7 @@ TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24)
// currency amount (AMM)
// currency amount (more)
TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
TYPED_SFIELD(sfEPrice, AMOUNT, 27)
@@ -326,6 +332,7 @@ TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43)
TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44)
TYPED_SFIELD(sfAmountCommitment, VL, 45)
TYPED_SFIELD(sfBalanceCommitment, VL, 46)
TYPED_SFIELD(sfBytecode, VL, 47)
// account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1)

View File

@@ -50,11 +50,13 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
NoPriv,
({
{sfDestination, SoeRequired},
{sfDestinationTag, SoeOptional},
{sfAmount, SoeRequired, SoeMptSupported},
{sfCondition, SoeOptional},
{sfCancelAfter, SoeOptional},
{sfFinishAfter, SoeOptional},
{sfDestinationTag, SoeOptional},
{sfBytecode, SoeOptional},
{sfData, SoeOptional},
}))
/** This transaction type completes an existing escrow. */
@@ -71,6 +73,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish,
{sfFulfillment, SoeOptional},
{sfCondition, SoeOptional},
{sfCredentialIDs, SoeOptional},
{sfGas, SoeOptional},
}))
@@ -1228,6 +1231,10 @@ TRANSACTION(ttFEE, 101, SetFee,
{sfBaseFeeDrops, SoeOptional},
{sfReserveBaseDrops, SoeOptional},
{sfReserveIncrementDrops, SoeOptional},
// Smart Escrow fields
{sfGasLimit, SoeOptional},
{sfBytecodeSizeLimit, SoeOptional},
{sfGasPrice, SoeOptional},
}))
/** This system-generated transaction type is used to update the network's negative UNL

View File

@@ -252,6 +252,9 @@ JSS(expected_date); // out: any (warnings)
JSS(expected_date_UTC); // out: any (warnings)
JSS(expected_ledger_size); // out: TxQ
JSS(expiration); // out: AccountOffers, AccountChannels, ValidatorList, amm_info
JSS(gas_limit); // out: NetworkOPs
JSS(bytecode_size_limit); // out: NetworkOPs
JSS(gas_price); // out: NetworkOPs
JSS(fail_hard); // in: Sign, Submit
JSS(failed); // out: InboundLedger
JSS(feature); // in: Feature

View File

@@ -174,6 +174,54 @@ public:
return this->sle_->isFieldPresent(sfFinishAfter);
}
/**
* @brief Get sfBytecode (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getBytecode() const
{
if (hasBytecode())
return this->sle_->at(sfBytecode);
return std::nullopt;
}
/**
* @brief Check if sfBytecode is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasBytecode() const
{
return this->sle_->isFieldPresent(sfBytecode);
}
/**
* @brief Get sfData (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getData() const
{
if (hasData())
return this->sle_->at(sfData);
return std::nullopt;
}
/**
* @brief Check if sfData is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasData() const
{
return this->sle_->isFieldPresent(sfData);
}
/**
* @brief Get sfSourceTag (SoeOptional)
* @return The field value, or std::nullopt if not present.
@@ -453,6 +501,28 @@ public:
return *this;
}
/**
* @brief Set sfBytecode (SoeOptional)
* @return Reference to this builder for method chaining.
*/
EscrowBuilder&
setBytecode(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfBytecode] = value;
return *this;
}
/**
* @brief Set sfData (SoeOptional)
* @return Reference to this builder for method chaining.
*/
EscrowBuilder&
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfData] = value;
return *this;
}
/**
* @brief Set sfSourceTag (SoeOptional)
* @return Reference to this builder for method chaining.

View File

@@ -213,6 +213,78 @@ public:
return this->sle_->isFieldPresent(sfReserveIncrementDrops);
}
/**
* @brief Get sfGasLimit (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getGasLimit() const
{
if (hasGasLimit())
return this->sle_->at(sfGasLimit);
return std::nullopt;
}
/**
* @brief Check if sfGasLimit is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasGasLimit() const
{
return this->sle_->isFieldPresent(sfGasLimit);
}
/**
* @brief Get sfBytecodeSizeLimit (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getBytecodeSizeLimit() const
{
if (hasBytecodeSizeLimit())
return this->sle_->at(sfBytecodeSizeLimit);
return std::nullopt;
}
/**
* @brief Check if sfBytecodeSizeLimit is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasBytecodeSizeLimit() const
{
return this->sle_->isFieldPresent(sfBytecodeSizeLimit);
}
/**
* @brief Get sfGasPrice (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getGasPrice() const
{
if (hasGasPrice())
return this->sle_->at(sfGasPrice);
return std::nullopt;
}
/**
* @brief Check if sfGasPrice is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasGasPrice() const
{
return this->sle_->isFieldPresent(sfGasPrice);
}
/**
* @brief Get sfPreviousTxnID (SoeOptional)
* @return The field value, or std::nullopt if not present.
@@ -375,6 +447,39 @@ public:
return *this;
}
/**
* @brief Set sfGasLimit (SoeOptional)
* @return Reference to this builder for method chaining.
*/
FeeSettingsBuilder&
setGasLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfGasLimit] = value;
return *this;
}
/**
* @brief Set sfBytecodeSizeLimit (SoeOptional)
* @return Reference to this builder for method chaining.
*/
FeeSettingsBuilder&
setBytecodeSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfBytecodeSizeLimit] = value;
return *this;
}
/**
* @brief Set sfGasPrice (SoeOptional)
* @return Reference to this builder for method chaining.
*/
FeeSettingsBuilder&
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfGasPrice] = value;
return *this;
}
/**
* @brief Set sfPreviousTxnID (SoeOptional)
* @return Reference to this builder for method chaining.

View File

@@ -58,6 +58,32 @@ public:
return this->tx_->at(sfDestination);
}
/**
* @brief Get sfDestinationTag (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getDestinationTag() const
{
if (hasDestinationTag())
{
return this->tx_->at(sfDestinationTag);
}
return std::nullopt;
}
/**
* @brief Check if sfDestinationTag is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasDestinationTag() const
{
return this->tx_->isFieldPresent(sfDestinationTag);
}
/**
* @brief Get sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
@@ -149,29 +175,55 @@ public:
}
/**
* @brief Get sfDestinationTag (SoeOptional)
* @brief Get sfBytecode (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getDestinationTag() const
protocol_autogen::Optional<SF_VL::type::value_type>
getBytecode() const
{
if (hasDestinationTag())
if (hasBytecode())
{
return this->tx_->at(sfDestinationTag);
return this->tx_->at(sfBytecode);
}
return std::nullopt;
}
/**
* @brief Check if sfDestinationTag is present.
* @brief Check if sfBytecode is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasDestinationTag() const
hasBytecode() const
{
return this->tx_->isFieldPresent(sfDestinationTag);
return this->tx_->isFieldPresent(sfBytecode);
}
/**
* @brief Get sfData (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getData() const
{
if (hasData())
{
return this->tx_->at(sfData);
}
return std::nullopt;
}
/**
* @brief Check if sfData is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasData() const
{
return this->tx_->isFieldPresent(sfData);
}
};
@@ -232,6 +284,17 @@ public:
return *this;
}
/**
* @brief Set sfDestinationTag (SoeOptional)
* @return Reference to this builder for method chaining.
*/
EscrowCreateBuilder&
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfDestinationTag] = value;
return *this;
}
/**
* @brief Set sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
@@ -278,13 +341,24 @@ public:
}
/**
* @brief Set sfDestinationTag (SoeOptional)
* @brief Set sfBytecode (SoeOptional)
* @return Reference to this builder for method chaining.
*/
EscrowCreateBuilder&
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
setBytecode(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfDestinationTag] = value;
object_[sfBytecode] = value;
return *this;
}
/**
* @brief Set sfData (SoeOptional)
* @return Reference to this builder for method chaining.
*/
EscrowCreateBuilder&
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfData] = value;
return *this;
}

View File

@@ -146,6 +146,32 @@ public:
{
return this->tx_->isFieldPresent(sfCredentialIDs);
}
/**
* @brief Get sfGas (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getGas() const
{
if (hasGas())
{
return this->tx_->at(sfGas);
}
return std::nullopt;
}
/**
* @brief Check if sfGas is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasGas() const
{
return this->tx_->isFieldPresent(sfGas);
}
};
/**
@@ -249,6 +275,17 @@ public:
return *this;
}
/**
* @brief Set sfGas (SoeOptional)
* @return Reference to this builder for method chaining.
*/
EscrowFinishBuilder&
setGas(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfGas] = value;
return *this;
}
/**
* @brief Build and return the EscrowFinish wrapper.
* @param publicKey The public key for signing.

View File

@@ -254,6 +254,84 @@ public:
{
return this->tx_->isFieldPresent(sfReserveIncrementDrops);
}
/**
* @brief Get sfGasLimit (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getGasLimit() const
{
if (hasGasLimit())
{
return this->tx_->at(sfGasLimit);
}
return std::nullopt;
}
/**
* @brief Check if sfGasLimit is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasGasLimit() const
{
return this->tx_->isFieldPresent(sfGasLimit);
}
/**
* @brief Get sfBytecodeSizeLimit (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getBytecodeSizeLimit() const
{
if (hasBytecodeSizeLimit())
{
return this->tx_->at(sfBytecodeSizeLimit);
}
return std::nullopt;
}
/**
* @brief Check if sfBytecodeSizeLimit is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasBytecodeSizeLimit() const
{
return this->tx_->isFieldPresent(sfBytecodeSizeLimit);
}
/**
* @brief Get sfGasPrice (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getGasPrice() const
{
if (hasGasPrice())
{
return this->tx_->at(sfGasPrice);
}
return std::nullopt;
}
/**
* @brief Check if sfGasPrice is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasGasPrice() const
{
return this->tx_->isFieldPresent(sfGasPrice);
}
};
/**
@@ -386,6 +464,39 @@ public:
return *this;
}
/**
* @brief Set sfGasLimit (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SetFeeBuilder&
setGasLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfGasLimit] = value;
return *this;
}
/**
* @brief Set sfBytecodeSizeLimit (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SetFeeBuilder&
setBytecodeSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfBytecodeSizeLimit] = value;
return *this;
}
/**
* @brief Set sfGasPrice (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SetFeeBuilder&
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfGasPrice] = value;
return *this;
}
/**
* @brief Build and return the SetFee wrapper.
* @param publicKey The public key for signing.

View File

@@ -15,6 +15,7 @@
#include <xrpl/protocol/XRPAmount.h>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <utility>
@@ -95,6 +96,24 @@ public:
view_->deliver(amount);
}
/**
* Sets the gas used in the metadata
*/
void
setGasUsed(std::uint32_t const gasUsed)
{
gasUsed_ = gasUsed;
}
/**
* Sets the gas used in the metadata
*/
void
setVMReturnCode(std::int32_t const vmReturnCode)
{
vmReturnCode_ = vmReturnCode;
}
/**
* Discard changes and start fresh.
*/
@@ -163,6 +182,8 @@ private:
// The ID of the batch transaction we are executing under, if set.
std::optional<uint256 const> parentBatchId_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> vmReturnCode_;
};
} // namespace xrpl

View File

@@ -20,15 +20,21 @@ public:
{
}
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static bool
checkExtraFeatures(PreflightContext const& ctx);
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
preflightSigValidated(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);

View File

@@ -0,0 +1,530 @@
#pragma once
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/insight/Event.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <cstdint>
#include <expected>
#include <functional>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
namespace xrpl {
namespace wasm_float {
std::string
floatToString(Slice const& data);
std::expected<Bytes, HostFunctionError>
floatFromIntImpl(int64_t x, int32_t mode);
std::expected<Bytes, HostFunctionError>
floatFromUintImpl(uint64_t x, int32_t mode);
std::expected<Bytes, HostFunctionError>
floatFromSTAmountImpl(STAmount const& x, int32_t mode);
std::expected<Bytes, HostFunctionError>
floatFromSTNumberImpl(STNumber const& x, int32_t mode);
std::expected<int64_t, HostFunctionError>
floatToIntImpl(Slice const& x, int32_t mode);
std::expected<FloatPair, HostFunctionError>
floatToMantExpImpl(Slice const& x);
std::expected<Bytes, HostFunctionError>
floatFromMantExpImpl(int64_t mantissa, int32_t exponent, int32_t mode);
std::expected<int32_t, HostFunctionError>
floatCompareImpl(Slice const& x, Slice const& y);
std::expected<Bytes, HostFunctionError>
floatAddImpl(Slice const& x, Slice const& y, int32_t mode);
std::expected<Bytes, HostFunctionError>
floatSubtractImpl(Slice const& x, Slice const& y, int32_t mode);
std::expected<Bytes, HostFunctionError>
floatMultiplyImpl(Slice const& x, Slice const& y, int32_t mode);
std::expected<Bytes, HostFunctionError>
floatDivideImpl(Slice const& x, Slice const& y, int32_t mode);
std::expected<Bytes, HostFunctionError>
floatRootImpl(Slice const& x, int32_t n, int32_t mode);
std::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_;
}
[[nodiscard]] virtual beast::insight::Event
executionTimeEvent(std::string_view name) const
{
return {};
}
// LCOV_EXCL_START
[[nodiscard]] virtual bool
checkSelf() const
{
return true;
}
[[nodiscard]] [[nodiscard]] virtual std::expected<std::uint32_t, HostFunctionError>
getLedgerSqn() const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<std::uint32_t, HostFunctionError>
getParentLedgerTime() const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Hash, HostFunctionError>
getParentLedgerHash() const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<uint32_t, HostFunctionError>
getBaseFee() const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
isAmendmentEnabled(uint256 const& amendmentId) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
isAmendmentEnabled(std::string_view const& amendmentName) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
virtual std::expected<int32_t, HostFunctionError>
cacheLedgerObj(uint256 const& objId, int32_t cacheIdx)
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
getTxField(SField const& fname) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
getCurrentLedgerObjField(SField const& fname) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
getLedgerObjField(int32_t cacheIdx, SField const& fname) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
getTxNestedField(FieldLocator const& locator) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
getCurrentLedgerObjNestedField(FieldLocator const& locator) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
getTxArrayLen(SField const& fname) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
getCurrentLedgerObjArrayLen(SField const& fname) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
getTxNestedArrayLen(FieldLocator const& locator) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
virtual std::expected<int32_t, HostFunctionError>
updateData(Slice const& data)
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Hash, HostFunctionError>
computeSha512HalfHash(Slice const& data) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
accountKeylet(AccountID const& account) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
ammKeylet(Asset const& issue1, Asset const& issue2) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
checkKeylet(AccountID const& account, std::uint32_t seq) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType)
const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
didKeylet(AccountID const& account) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
delegateKeylet(AccountID const& account, AccountID const& authorize) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
escrowKeylet(AccountID const& account, std::uint32_t seq) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
trustLineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency)
const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
mptokenIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
mptokenKeylet(MPTID const& mptid, AccountID const& holder) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
nftokenOfferKeylet(AccountID const& account, std::uint32_t seq) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
offerKeylet(AccountID const& account, std::uint32_t seq) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
oracleKeylet(AccountID const& account, std::uint32_t docId) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
paychannelKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq)
const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
signerListKeylet(AccountID const& account) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
ticketKeylet(AccountID const& account, std::uint32_t seq) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
vaultKeylet(AccountID const& account, std::uint32_t seq) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
getNFT(AccountID const& account, uint256 const& nftId) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
getNFTIssuer(uint256 const& nftId) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<std::uint32_t, HostFunctionError>
getNFTTaxon(uint256 const& nftId) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
getNFTFlags(uint256 const& nftId) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
getNFTTransferFee(uint256 const& nftId) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<std::uint32_t, HostFunctionError>
getNFTSequence(uint256 const& nftId) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
trace(std::string_view const& msg, Slice const& data, bool asHex) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
traceNum(std::string_view const& msg, int64_t data) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
traceAccount(std::string_view const& msg, AccountID const& account) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
traceFloat(std::string_view const& msg, Slice const& data) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
traceAmount(std::string_view const& msg, STAmount const& amount) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
floatFromInt(int64_t x, int32_t mode) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
floatFromUint(uint64_t x, int32_t mode) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
floatFromSTAmount(STAmount const& x, int32_t mode) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
floatFromSTNumber(STNumber const& x, int32_t mode) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int64_t, HostFunctionError>
floatToInt(Slice const& x, int32_t mode) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<FloatPair, HostFunctionError>
floatToMantExp(Slice const& x) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<int32_t, HostFunctionError>
floatCompare(Slice const& x, Slice const& y) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
floatAdd(Slice const& x, Slice const& y, int32_t mode) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
floatSubtract(Slice const& x, Slice const& y, int32_t mode) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
floatMultiply(Slice const& x, Slice const& y, int32_t mode) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
floatDivide(Slice const& x, Slice const& y, int32_t mode) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
floatRoot(Slice const& x, int32_t n, int32_t mode) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
[[nodiscard]] [[nodiscard]] virtual std::expected<Bytes, HostFunctionError>
floatPower(Slice const& x, int32_t n, int32_t mode) const
{
return std::unexpected(HostFunctionError::Unimplemented);
}
virtual ~HostFunctions() = default;
// LCOV_EXCL_STOP
};
using HFRef = std::reference_wrapper<HostFunctions>;
} // namespace xrpl

View File

@@ -0,0 +1,313 @@
#pragma once
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/insight/Event.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/CollectorManager.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/wasm/HostFunc.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <string_view>
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:
std::expected<std::shared_ptr<SLE const>, HostFunctionError>
getCurrentLedgerObj() const
{
if (!currentLedgerObj_)
currentLedgerObj_ = ctx_.view().read(leKey_);
if (*currentLedgerObj_)
return *currentLedgerObj_;
return std::unexpected(HostFunctionError::LedgerObjNotFound);
}
std::expected<int32_t, HostFunctionError>
normalizeCacheIndex(int32_t cacheIdx) const
{
--cacheIdx;
if (cacheIdx < 0 || cacheIdx >= maxCache)
return std::unexpected(HostFunctionError::SlotOutRange);
if (!cache_[cacheIdx])
return std::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)
{
}
beast::insight::Event
executionTimeEvent(std::string_view name) const override
{
return ctx_.registry.get()
.getCollectorManager()
.group(std::string{name.data(), name.size()})
->makeEvent("finish_time");
}
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_;
}
std::expected<std::uint32_t, HostFunctionError>
getLedgerSqn() const override;
std::expected<std::uint32_t, HostFunctionError>
getParentLedgerTime() const override;
std::expected<Hash, HostFunctionError>
getParentLedgerHash() const override;
std::expected<std::uint32_t, HostFunctionError>
getBaseFee() const override;
std::expected<int32_t, HostFunctionError>
isAmendmentEnabled(uint256 const& amendmentId) const override;
std::expected<int32_t, HostFunctionError>
isAmendmentEnabled(std::string_view const& amendmentName) const override;
std::expected<int32_t, HostFunctionError>
cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) override;
std::expected<Bytes, HostFunctionError>
getTxField(SField const& fname) const override;
std::expected<Bytes, HostFunctionError>
getCurrentLedgerObjField(SField const& fname) const override;
std::expected<Bytes, HostFunctionError>
getLedgerObjField(int32_t cacheIdx, SField const& fname) const override;
std::expected<Bytes, HostFunctionError>
getTxNestedField(FieldLocator const& locator) const override;
std::expected<Bytes, HostFunctionError>
getCurrentLedgerObjNestedField(FieldLocator const& locator) const override;
std::expected<Bytes, HostFunctionError>
getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const override;
std::expected<int32_t, HostFunctionError>
getTxArrayLen(SField const& fname) const override;
std::expected<int32_t, HostFunctionError>
getCurrentLedgerObjArrayLen(SField const& fname) const override;
std::expected<int32_t, HostFunctionError>
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const override;
std::expected<int32_t, HostFunctionError>
getTxNestedArrayLen(FieldLocator const& locator) const override;
std::expected<int32_t, HostFunctionError>
getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const override;
std::expected<int32_t, HostFunctionError>
getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) const override;
std::expected<int32_t, HostFunctionError>
updateData(Slice const& data) override;
std::expected<int32_t, HostFunctionError>
checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey)
const override;
std::expected<Hash, HostFunctionError>
computeSha512HalfHash(Slice const& data) const override;
std::expected<Bytes, HostFunctionError>
accountKeylet(AccountID const& account) const override;
std::expected<Bytes, HostFunctionError>
ammKeylet(Asset const& issue1, Asset const& issue2) const override;
std::expected<Bytes, HostFunctionError>
checkKeylet(AccountID const& account, std::uint32_t seq) const override;
std::expected<Bytes, HostFunctionError>
credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType)
const override;
std::expected<Bytes, HostFunctionError>
didKeylet(AccountID const& account) const override;
std::expected<Bytes, HostFunctionError>
delegateKeylet(AccountID const& account, AccountID const& authorize) const override;
std::expected<Bytes, HostFunctionError>
depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const override;
std::expected<Bytes, HostFunctionError>
escrowKeylet(AccountID const& account, std::uint32_t seq) const override;
std::expected<Bytes, HostFunctionError>
trustLineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency)
const override;
std::expected<Bytes, HostFunctionError>
mptokenIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const override;
std::expected<Bytes, HostFunctionError>
mptokenKeylet(MPTID const& mptid, AccountID const& holder) const override;
std::expected<Bytes, HostFunctionError>
nftokenOfferKeylet(AccountID const& account, std::uint32_t seq) const override;
std::expected<Bytes, HostFunctionError>
offerKeylet(AccountID const& account, std::uint32_t seq) const override;
std::expected<Bytes, HostFunctionError>
oracleKeylet(AccountID const& account, std::uint32_t docId) const override;
std::expected<Bytes, HostFunctionError>
paychannelKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq)
const override;
std::expected<Bytes, HostFunctionError>
permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const override;
std::expected<Bytes, HostFunctionError>
signerListKeylet(AccountID const& account) const override;
std::expected<Bytes, HostFunctionError>
ticketKeylet(AccountID const& account, std::uint32_t seq) const override;
std::expected<Bytes, HostFunctionError>
vaultKeylet(AccountID const& account, std::uint32_t seq) const override;
std::expected<Bytes, HostFunctionError>
getNFT(AccountID const& account, uint256 const& nftId) const override;
std::expected<Bytes, HostFunctionError>
getNFTIssuer(uint256 const& nftId) const override;
std::expected<std::uint32_t, HostFunctionError>
getNFTTaxon(uint256 const& nftId) const override;
std::expected<int32_t, HostFunctionError>
getNFTFlags(uint256 const& nftId) const override;
std::expected<int32_t, HostFunctionError>
getNFTTransferFee(uint256 const& nftId) const override;
std::expected<std::uint32_t, HostFunctionError>
getNFTSequence(uint256 const& nftId) const override;
std::expected<int32_t, HostFunctionError>
trace(std::string_view const& msg, Slice const& data, bool asHex) const override;
std::expected<int32_t, HostFunctionError>
traceNum(std::string_view const& msg, int64_t data) const override;
std::expected<int32_t, HostFunctionError>
traceAccount(std::string_view const& msg, AccountID const& account) const override;
std::expected<int32_t, HostFunctionError>
traceFloat(std::string_view const& msg, Slice const& data) const override;
std::expected<int32_t, HostFunctionError>
traceAmount(std::string_view const& msg, STAmount const& amount) const override;
std::expected<Bytes, HostFunctionError>
floatFromInt(int64_t x, int32_t mode) const override;
std::expected<Bytes, HostFunctionError>
floatFromUint(uint64_t x, int32_t mode) const override;
std::expected<Bytes, HostFunctionError>
floatFromSTAmount(STAmount const& x, int32_t mode) const override;
std::expected<Bytes, HostFunctionError>
floatFromSTNumber(STNumber const& x, int32_t mode) const override;
std::expected<int64_t, HostFunctionError>
floatToInt(Slice const& x, int32_t mode) const override;
std::expected<FloatPair, HostFunctionError>
floatToMantExp(Slice const& x) const override;
std::expected<Bytes, HostFunctionError>
floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const override;
std::expected<int32_t, HostFunctionError>
floatCompare(Slice const& x, Slice const& y) const override;
std::expected<Bytes, HostFunctionError>
floatAdd(Slice const& x, Slice const& y, int32_t mode) const override;
std::expected<Bytes, HostFunctionError>
floatSubtract(Slice const& x, Slice const& y, int32_t mode) const override;
std::expected<Bytes, HostFunctionError>
floatMultiply(Slice const& x, Slice const& y, int32_t mode) const override;
std::expected<Bytes, HostFunctionError>
floatDivide(Slice const& x, Slice const& y, int32_t mode) const override;
std::expected<Bytes, HostFunctionError>
floatRoot(Slice const& x, int32_t n, int32_t mode) const override;
std::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/HostFunc.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 trustLineKeylet_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* trustLineKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
using mptokenIssuanceKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* mptokenIssuanceKeylet_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 nftokenOfferKeylet_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* nftokenOfferKeylet_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 paychannelKeylet_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* paychannelKeylet_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 signerListKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* signerListKeylet_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 getNFTSequence_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t* getNFTSequence_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
`std::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
`escrowFunctionName` (currently `"escrow_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 std::expected<ReturnType, HostFunctionError>
myNewFunction(ParamType1 param1, ParamType2 param2)
{
return std::unexpected(HostFunctionError::INTERNAL);
}
```
### 2. Add to HostFuncImpl.h (Declaration)
Add the method override declaration in `WasmHostFunctionsImpl`:
```cpp
std::expected<ReturnType, HostFunctionError>
myNewFunction(ParamType1 param1, ParamType2 param2) override;
```
### 3. Implement in detail/HostFuncImpl\*.cpp
Add the implementation in the appropriate file:
```cpp
std::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,241 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/protocol/TER.h>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <stdexcept>
#include <string_view>
#include <type_traits>
#include <utility>
#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)
{
}
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,126 @@
#pragma once
#include <xrpl/tx/wasm/HostFunc.h>
#include <xrpl/tx/wasm/WasmCommon.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>
#include <cstdint>
#include <optional>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
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 = boost::mpl::at_c<Mpl, N>::type;
if constexpr (std::is_pointer_v<at> || 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);
}
}
template <typename>
inline constexpr bool wasmDependentFalse = false;
template <typename Rt>
void
WasmImpRet(WasmImportFunc& e)
{
if constexpr (std::is_pointer_v<Rt> || 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 = bft::result_type<F>::type;
using pt = 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,97 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/wasm/HostFunc.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <xrpl/tx/wasm/WasmImportsHelper.h>
#include <cstdint>
#include <expected>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
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 = "escrow_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();
std::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);
std::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,462 @@
#pragma once
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/wasm/HostFunc.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <xrpl/tx/wasm/WasmImportsHelper.h>
#include <xrpl/tx/wasm/WasmVM.h>
#include <wasm.h>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <memory>
#include <mutex>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
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 const*
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();
std::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();
}
std::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

@@ -7,8 +7,10 @@ let
inherit (import ./packages.nix { inherit pkgs; })
commonPackages
gccPackage
gccVersion
llvmPackages
llvmVersion
mkVersionedToolLinks
;
# Underlying compiler toolchains to wrap (versions pinned in packages.nix).
@@ -127,6 +129,25 @@ in
customGcov
customClangForCiEnv
customBinutils
(mkVersionedToolLinks {
name = "gcc";
package = customGcc;
version = gccVersion;
tools = [
"gcc"
"g++"
"cpp"
];
})
(mkVersionedToolLinks {
name = "clang";
package = customClang;
version = llvmVersion;
tools = [
"clang"
"clang++"
];
})
# CA certificate bundle so HTTPS clients (git, curl, conan) can verify
# TLS connections without ca-certificates being installed in the system.
pkgs.cacert

View File

@@ -3,7 +3,9 @@ let
inherit (import ./packages.nix { inherit pkgs; })
commonPackages
gccVersion
llvmVersion
llvmPackages
mkVersionedToolLinks
;
# Plain nixpkgs stdenvs — no custom glibc, unlike ci-env.nix.
@@ -15,6 +17,8 @@ let
{
stdenv,
compilerName,
version ? null,
versionedTools ? [ ],
}:
let
compilerVersion =
@@ -25,9 +29,15 @@ let
echo "Compiler: "
${compilerName} --version
'';
versionedLinks = pkgs.lib.optional (version != null) (mkVersionedToolLinks {
name = compilerName;
package = stdenv.cc;
inherit version;
tools = versionedTools;
});
in
(pkgs.mkShell.override { inherit stdenv; }) {
packages = commonPackages;
packages = commonPackages ++ versionedLinks;
shellHook = ''
echo "Welcome to xrpld development shell";
${compilerVersion}
@@ -41,11 +51,22 @@ rec {
gcc = makeShell {
stdenv = gccStdenv;
compilerName = "gcc";
version = gccVersion;
versionedTools = [
"gcc"
"g++"
"cpp"
];
};
clang = makeShell {
stdenv = clangStdenv;
compilerName = "clang";
version = llvmVersion;
versionedTools = [
"clang"
"clang++"
];
};
# Nix provides no compiler; use the one from your system (e.g. Apple Clang).

View File

@@ -12,6 +12,7 @@ COPY nix/packages.nix /tmp/build/nix/packages.nix
COPY nix/utils.nix /tmp/build/nix/utils.nix
COPY flake.nix /tmp/build/
COPY flake.lock /tmp/build/
COPY rust-toolchain.toml /tmp/build/
WORKDIR /tmp/build
FROM builder-source AS builder

View File

@@ -47,7 +47,9 @@ work without `ca-certificates` being installed in the base image.
[`test_files/cpp/sources/`](./test_files/cpp/sources) with both `g++` and
`clang++`, and sanitizers, and
- compiles the Rust test programs in
[`test_files/rust/sources/`](./test_files/rust/sources) with `rustc`.
[`test_files/rust/sources/`](./test_files/rust/sources) with `rustc`, and
builds the [`test_files/rust/proc_macro/`](./test_files/rust/proc_macro)
workspace with `cargo` to exercise proc-macro dylib loading.
3. **`tester`** — Start again from a clean `BASE_IMAGE` (no Nix toolchain),
install only the sanitizer runtime libraries
([`install-sanitizer-libs.sh`](./install-sanitizer-libs.sh)), and run the
@@ -76,20 +78,23 @@ toolchain being present at runtime. Two pieces make that work:
[`loader-path.sh`](./loader-path.sh) reports the expected loader path for the
current architecture, so we can patch the binaries to use the correct loader.
The build then verifies all of this end to end: the C++ test programs in
`test_files/cpp/sources/` (a regular binary plus ASan/TSan/UBSan variants) and
the Rust test programs in `test_files/rust/sources/` (a hello binary plus panic
and overflow-check variants) are compiled in `final`, their `PT_INTERP` is
patched to the target loader, and they are run in the clean `tester` stage to
confirm each emits the expected diagnostic on a stock base image.
The build then verifies all of this end to end, and the C++ and Rust programs
go through the same pipeline: each is compiled in `final`, has its `PT_INTERP`
patched to the target loader, and is then run in the clean `tester` stage to
confirm it emits the expected diagnostic on a stock base image. The C++ programs
are in `test_files/cpp/sources/` (a regular binary plus ASan/TSan/UBSan
variants); the Rust programs are in `test_files/rust/sources/` (a hello binary
plus panic and overflow-check variants), plus the `test_files/rust/proc_macro/`
workspace — a crate whose compilation additionally loads a proc-macro dylib, and
whose resulting binary is patched and run like the others.
## Files
| File | Purpose |
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| [`./Dockerfile`](./Dockerfile) | Multi-stage build described above. |
| [`./loader-path.sh`](./loader-path.sh) | Print the dynamic-linker (`PT_INTERP`) path for the current architecture. |
| [`./test_files/cpp/`](./test_files/cpp) | C++ sanitizer smoke test: sources + compile/run scripts. |
| [`./test_files/rust/`](./test_files/rust) | Rust rustc smoke test: sources + compile/run scripts. |
| [`/bin/check-tools.sh`](../../bin/check-tools.sh) | Verify every expected tools are present and runnable. |
| [`/bin/install-sanitizer-libs.sh`](../../bin/install-sanitizer-libs.sh) | Install `libasan`/`libtsan`/`libubsan` runtimes on the supported base images. |
| File | Purpose |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| [`./Dockerfile`](./Dockerfile) | Multi-stage build described above. |
| [`./loader-path.sh`](./loader-path.sh) | Print the dynamic-linker (`PT_INTERP`) path for the current architecture. |
| [`./test_files/cpp/`](./test_files/cpp) | C++ sanitizer smoke test: sources + compile/run scripts. |
| [`./test_files/rust/`](./test_files/rust) | Rust smoke test: rustc sources + a cargo proc-macro workspace + compile/run scripts. |
| [`/bin/check-tools.sh`](../../bin/check-tools.sh) | Verify every expected tools are present and runnable. |
| [`/bin/install-sanitizer-libs.sh`](../../bin/install-sanitizer-libs.sh) | Install `libasan`/`libtsan`/`libubsan` runtimes on the supported base images. |

View File

@@ -40,6 +40,29 @@ compile hello
compile panic
compile overflow "-C overflow-checks=on"
function compile_proc_macro() {
local proj="${src_dir}/../proc_macro"
echo "=== Building proc-macro workspace (cargo) ==="
cargo build --manifest-path "${proj}/Cargo.toml" --offline
local built="${proj}/target/debug/test_macro"
if [ ! -f "${built}" ]; then
echo "ERROR: built test_macro binary not found at ${built}" >&2
exit 1
fi
local binary="${dst_dir}/proc_macro"
cp "${built}" "${binary}"
echo "=== Patching ${binary} to use ${loader} as PT_INTERP ==="
patchelf --set-interpreter "${loader}" --remove-rpath "${binary}"
rm -rf "${proj}/target"
}
compile_proc_macro
echo "=== All binaries compiled ==="
ls -la "${dst_dir}"

View File

@@ -0,0 +1,14 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "echo_macro"
version = "0.0.0"
[[package]]
name = "test_macro"
version = "0.0.0"
dependencies = [
"echo_macro",
]

View File

@@ -0,0 +1,3 @@
[workspace]
resolver = "2"
members = ["echo_macro", "test_macro"]

View File

@@ -0,0 +1,8 @@
[package]
name = "echo_macro"
version = "0.0.0"
edition = "2024"
publish = false
[lib]
proc-macro = true

View File

@@ -0,0 +1,6 @@
use proc_macro::TokenStream;
#[proc_macro]
pub fn define_echo(item: TokenStream) -> TokenStream {
format!("fn echo() -> u32 {{ {item} }}").parse().unwrap()
}

View File

@@ -0,0 +1,8 @@
[package]
name = "test_macro"
version = "0.0.0"
edition = "2024"
publish = false
[dependencies]
echo_macro = { path = "../echo_macro" }

View File

@@ -0,0 +1,9 @@
use echo_macro::define_echo;
define_echo!(42);
fn main() {
let a = echo();
println!("proc-macro answer = {a}");
assert_eq!(a, 42, "proc-macro expansion produced the wrong value");
}

View File

@@ -1,7 +1,7 @@
#!/bin/bash
# Run pre-compiled Rust binaries and confirm each emits its expected diagnostic.
# Binaries must already exist in <bins_dir> as <name> for name in
# {hello,panic,overflow}.
# {hello,panic,overflow,proc_macro}.
set -eo pipefail
@@ -54,12 +54,13 @@ declare -A expect=(
[hello]="Hello from main thread"
[panic]="explicit panic from test"
[overflow]="attempt to add with overflow"
[proc_macro]="proc-macro answer = 42"
)
for name in hello panic overflow; do
for name in hello panic overflow proc_macro; do
binary="${bins_dir}/${name}"
if [ "${name}" = "hello" ]; then
if [ "${name}" = "hello" ] || [ "${name}" = "proc_macro" ]; then
expected_rc=0
else
expected_rc=nonzero

View File

@@ -15,6 +15,55 @@ let
runClangTidy = pkgs.writeShellScriptBin "run-clang-tidy" ''
exec ${pkgs.python3}/bin/python3 ${llvmPackages.clang-unwrapped}/bin/run-clang-tidy "$@"
'';
rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml;
# Nix wraps its toolchain so that binaries are exposed only under unsuffixed
# names (gcc, g++, clang-tidy, ...). Several tools probe for a
# version-suffixed name first and fall back to a system binary on the PATH
# when Nix doesn't provide it:
# - Conan's Boost recipe looks up `g++-<major>` before plain `g++`.
# - bin/pre-commit/clang_tidy_check.py looks up `run-clang-tidy-<v>` and
# `clang-apply-replacements-<v>` before the unsuffixed names.
# On a host that also has the matching system binary (e.g. Ubuntu's
# `/usr/bin/g++-15` or `clang-tidy-22`) the probe escapes Nix and mixes a
# system tool into the Nix environment. Generate version-suffixed symlinks
# next to a package's tools so those probes resolve to the Nix ones.
#
# Compiler links must point at whichever compiler is active in a given
# environment (the plain stdenv compiler in the dev shell, the custom-glibc
# wrappers in ci-env.nix), so those callers pass their own `package`; the
# clang tooling is environment-independent and is linked in commonPackages.
mkVersionedToolLinks =
{
name,
package,
version,
tools,
}:
pkgs.linkFarm "${name}-${toString version}-versioned-links" (
map (tool: {
name = "bin/${tool}-${toString version}";
path = "${package}/bin/${tool}";
}) tools
);
clangToolLinks = mkVersionedToolLinks {
name = "clang-tools";
package = clangTools;
version = llvmVersion;
tools = [
"clang-tidy"
"clang-apply-replacements"
"clang-format"
];
};
runClangTidyLink = mkVersionedToolLinks {
name = "run-clang-tidy";
package = runClangTidy;
version = llvmVersion;
tools = [ "run-clang-tidy" ];
};
in
{
inherit
@@ -22,9 +71,12 @@ in
llvmVersion
gccPackage
llvmPackages
mkVersionedToolLinks
;
commonPackages = with pkgs; [
clangToolLinks
runClangTidyLink
ccache
clangbuildanalyzer
clangTools
@@ -63,14 +115,10 @@ in
vim
zip
# Rust packages
cargo
cargo-audit
cargo-llvm-cov
cargo-nextest
clippy
corrosion
rust-analyzer
rustc
rustfmt
rustToolchain
];
}

View File

@@ -1,4 +1,8 @@
{ nixpkgs, nixpkgs-custom-glibc }:
{
nixpkgs,
nixpkgs-custom-glibc,
rust-overlay,
}:
function:
nixpkgs.lib.genAttrs
[
@@ -10,7 +14,12 @@ nixpkgs.lib.genAttrs
(
system:
function {
pkgs = import nixpkgs { inherit system; };
# rust-overlay adds `pkgs.rust-bin`, from which we build the pinned Rust
# toolchain (see packages.nix). Consumed by both the CI image and dev shell.
pkgs = import nixpkgs {
inherit system;
overlays = [ (import rust-overlay) ];
};
# glibc 2.31 — matches the system libc on Ubuntu 20.04 LTS. Sourced
# from the nixpkgs snapshot pinned via the `nixpkgs-custom-glibc`
# flake input, so the build uses the compiler from that snapshot

4
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "1.95"
components = ["rustfmt", "clippy", "rust-analyzer"]
profile = "minimal"

View File

@@ -0,0 +1,22 @@
include(XrplAddBenchmark)
# Benchmark requirements.
find_package(benchmark REQUIRED)
# Custom target for all benchmarks defined in this file.
add_custom_target(xrpl.benchmarks)
# Common library dependencies for every benchmark module. `benchmark_main`
# supplies a `main()` that parses the standard Google Benchmark CLI flags
# (`--benchmark_filter`, `--benchmark_format`, ...), so no per-module main.cpp
# is needed.
add_library(xrpl.imports.bench INTERFACE)
target_link_libraries(
xrpl.imports.bench
INTERFACE benchmark::benchmark_main xrpl.libxrpl
)
# One benchmark executable for each module.
xrpl_add_benchmark(nodestore)
target_link_libraries(xrpl.bench.nodestore PRIVATE xrpl.imports.bench)
add_dependencies(xrpl.benchmarks xrpl.bench.nodestore)

View File

@@ -0,0 +1,329 @@
#include <xrpl/nodestore/Backend.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/nodestore/Types.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/nodestore/NodeStoreBench.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace xrpl::NodeStore {
namespace {
constexpr std::size_t kPoolSizes[] = {1000, 10000, 100000};
constexpr int kThreadCounts[] = {1, 4, 8};
constexpr std::size_t kBatchSize = 256;
constexpr std::string_view kNamePrefix = "BM_Backend_";
constexpr std::string_view kNameSeparator = "/";
struct RunState
{
std::unique_ptr<BackendHarness> harness;
Batch present; // prefix-1 objects, eligible to be stored
Batch recent; // prefix-1 objects in the "future" key space
std::vector<uint256> missing; // prefix-2 keys that are never stored
std::vector<std::size_t> shuffle; // [0, poolSize) permutation for random-like access
std::size_t avgPayload = 0; // mean getData().size() over `present`
void
release()
{
harness.reset();
Batch{}.swap(present);
Batch{}.swap(recent);
std::vector<uint256>{}.swap(missing);
std::vector<std::size_t>{}.swap(shuffle);
}
};
struct SetupContext
{
RunState& rs;
Backend& backend;
std::size_t poolSize;
};
struct IterateContext
{
RunState& rs;
Backend& backend;
std::size_t index;
std::size_t poolSize;
};
struct Workload
{
std::string_view name;
std::function<void(SetupContext const&)> setup;
std::function<void(IterateContext const&)> iterate;
bool reportBytes = false; // SetBytesProcessed from rs.avgPayload
bool clobber = true; // ClobberMemory after the loop (false for pure stores)
bool pinToPool = false; // pin iterations to one pool sweep instead of autotuning
};
// One store() per iteration. Iterations are pinned to one pool sweep (per
// thread) so the index never wraps past the pool - otherwise NuDB::doInsert
// swallows key_exists and the workload degenerates into duplicate-detection
// no-ops.
Workload const kInsert{
.name = "Insert",
.setup =
[](SetupContext const& ctx) {
ctx.rs.present = makePool(1, ctx.poolSize);
ctx.rs.avgPayload = averagePayload(ctx.rs.present);
},
.iterate =
[](IterateContext const& ctx) {
auto& [rs, backend, index, poolSize] = ctx;
backend.store(rs.present[index % poolSize]);
},
.reportBytes = true,
.clobber = false,
.pinToPool = true,
};
// One fetch() of a present key (a hit) per iteration.
Workload const kFetch{
.name = "Fetch",
.setup =
[](SetupContext const& ctx) {
ctx.rs.present = makePool(1, ctx.poolSize);
ctx.rs.avgPayload = averagePayload(ctx.rs.present);
prepopulate(ctx.backend, ctx.rs.present);
},
.iterate =
[](IterateContext const& ctx) {
auto& [rs, backend, index, poolSize] = ctx;
std::shared_ptr<NodeObject> result;
backend.fetch(rs.present[index % poolSize]->getHash(), &result);
benchmark::DoNotOptimize(result);
},
.reportBytes = true,
};
// One fetch() of a never-stored key (a miss); the backend is left empty.
Workload const kMissing{
.name = "Missing",
.setup = [](SetupContext const& ctx) { ctx.rs.missing = makeMissingKeys(ctx.poolSize); },
.iterate =
[](IterateContext const& ctx) {
auto& [rs, backend, index, poolSize] = ctx;
std::shared_ptr<NodeObject> result;
backend.fetch(rs.missing[index % poolSize], &result);
benchmark::DoNotOptimize(result);
},
};
// 80% hits / 20% misses. The fetch index comes from a shuffle table so access
// is random-like without per-iteration RNG cost; sequential `index % poolSize`
// would be artificially cache-friendly to RocksDB's block cache.
Workload const kMixed{
.name = "Mixed",
.setup =
[](SetupContext const& ctx) {
ctx.rs.present = makePool(1, ctx.poolSize);
ctx.rs.missing = makeMissingKeys(ctx.poolSize);
ctx.rs.shuffle = makeShuffle(ctx.poolSize, /*seed=*/1);
prepopulate(ctx.backend, ctx.rs.present);
},
.iterate =
[](IterateContext const& ctx) {
auto& [rs, backend, index, poolSize] = ctx;
std::shared_ptr<NodeObject> result;
auto const pick = rs.shuffle[index % poolSize];
if (index % 5 == 0)
{
backend.fetch(rs.missing[pick], &result);
}
else
{
backend.fetch(rs.present[pick]->getHash(), &result);
}
benchmark::DoNotOptimize(result);
},
};
// An xrpld-like cycle: a hit, a maybe-miss recent fetch, and a store. The
// recent fetch uses the shuffle table (not `slot`) so it doesn't fetch the item
// it's about to store this iteration - which would give an all-miss-then-hit
// step instead of a smooth ramp. The store walks sequentially so each recent
// object is stored once.
Workload const kWork{
.name = "Work",
.setup =
[](SetupContext const& ctx) {
ctx.rs.present = makePool(1, ctx.poolSize);
ctx.rs.recent = makePool(1, ctx.poolSize, ctx.poolSize);
ctx.rs.shuffle = makeShuffle(ctx.poolSize, /*seed=*/2);
prepopulate(ctx.backend, ctx.rs.present);
},
.iterate =
[](IterateContext const& ctx) {
auto& [rs, backend, index, poolSize] = ctx;
auto const slot = index % poolSize;
auto const pick = rs.shuffle[slot];
std::shared_ptr<NodeObject> historical;
backend.fetch(rs.present[pick]->getHash(), &historical);
benchmark::DoNotOptimize(historical);
std::shared_ptr<NodeObject> recent;
backend.fetch(rs.recent[pick]->getHash(), &recent);
benchmark::DoNotOptimize(recent);
backend.store(rs.recent[slot]);
},
.clobber = true,
.pinToPool = true,
};
auto
makeRunner(Workload w, std::string cfg, std::shared_ptr<RunState> rs)
{
return [w = std::move(w), cfg = std::move(cfg), rs = std::move(rs)](benchmark::State& state) {
auto const poolSize = static_cast<std::size_t>(state.range(0));
if (state.thread_index() == 0)
{
rs->harness = std::make_unique<BackendHarness>(cfg);
w.setup(
SetupContext{.rs = *rs, .backend = *rs->harness->backend, .poolSize = poolSize});
}
std::size_t index = state.thread_index();
for (auto _ : state)
{
w.iterate(
IterateContext{
.rs = *rs,
.backend = *rs->harness->backend,
.index = index,
.poolSize = poolSize});
index += state.threads();
}
if (w.clobber)
benchmark::ClobberMemory();
state.SetItemsProcessed(state.iterations());
if (w.reportBytes)
state.SetBytesProcessed(static_cast<std::int64_t>(state.iterations() * rs->avgPayload));
if (state.thread_index() == 0)
rs->release();
};
}
// Register workload `w` against backend `bc`, choosing the registration shape
// from `w.pinToPool`.
void
registerWorkload(BackendConfig const& bc, Workload const& w)
{
std::string const cfg = bc.config;
std::string name{kNamePrefix};
name += w.name;
name += kNameSeparator;
name += bc.name;
if (!w.pinToPool)
{
auto rs = std::make_shared<RunState>();
auto* b = benchmark::RegisterBenchmark(name, makeRunner(w, cfg, rs));
b->RangeMultiplier(10)->Range(kPoolSizes[0], kPoolSizes[std::size(kPoolSizes) - 1]);
b->Threads(1)->Threads(4)->Threads(8)->UseRealTime();
return;
}
for (auto const poolSize : kPoolSizes)
{
for (auto const threads : kThreadCounts)
{
if (poolSize % static_cast<std::size_t>(threads) != 0)
continue;
auto rs = std::make_shared<RunState>();
benchmark::RegisterBenchmark(name, makeRunner(w, cfg, rs))
->Arg(poolSize)
->Iterations(poolSize / static_cast<std::size_t>(threads))
->Threads(threads)
->UseRealTime();
}
}
}
// One storeBatch() of kBatchSize objects per iteration. Single-threaded:
// Backend::storeBatch must not run concurrently with itself or store().
// Iterations are pinned to the batch count so the index never wraps into
// key_exists no-ops. Kept separate from Workload: batch slicing and the
// per-batch item/byte accounting don't fit the thread-axis mold.
void
registerStoreBatch(BackendConfig const& bc)
{
std::string const cfg = bc.config;
std::string name{kNamePrefix};
name += "StoreBatch";
name += kNameSeparator;
name += bc.name;
for (auto const poolSize : kPoolSizes)
{
auto const numBatches = poolSize / kBatchSize;
if (numBatches == 0)
continue;
auto rs = std::make_shared<RunState>();
benchmark::RegisterBenchmark(
name,
[rs, cfg](benchmark::State& state) {
auto const poolSize = static_cast<std::size_t>(state.range(0));
rs->harness = std::make_unique<BackendHarness>(cfg);
rs->present = makePool(1, poolSize);
rs->avgPayload = averagePayload(rs->present);
std::vector<Batch> const batches = sliceBatches(rs->present, kBatchSize);
if (batches.empty())
{
state.SkipWithError("pool smaller than one batch");
return;
}
std::size_t index = 0;
for (auto _ : state)
{
rs->harness->backend->storeBatch(batches[index % batches.size()]);
++index;
}
state.SetItemsProcessed(static_cast<std::int64_t>(state.iterations() * kBatchSize));
state.SetBytesProcessed(
static_cast<std::int64_t>(state.iterations() * kBatchSize * rs->avgPayload));
rs->release();
})
->Arg(poolSize)
->Iterations(numBatches);
}
}
[[maybe_unused]] bool const kRegistered = [] {
auto const workloads = std::to_array({&kInsert, &kFetch, &kMissing, &kMixed, &kWork});
for (auto const& bc : backendConfigs())
{
for (auto const* w : workloads)
registerWorkload(bc, *w);
registerStoreBatch(bc);
}
return true;
}();
} // namespace
} // namespace xrpl::NodeStore

View File

@@ -0,0 +1,243 @@
#include <xrpl/nodestore/Database.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/nodestore/Types.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/nodestore/NodeStoreBench.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace xrpl::NodeStore {
namespace {
// Number of distinct objects pre-generated per run.
constexpr std::size_t kDefaultPoolSize = 100000;
// Async read threads the Database spawns. Unused by the synchronous fetch path
// these benchmarks take; kept fixed so runs are comparable.
constexpr int kReadThreads = 4;
constexpr std::string_view kNamePrefix = "BM_Database_";
constexpr std::string_view kNameSeparator = "/";
struct RunState
{
std::unique_ptr<DatabaseHarness> harness;
Batch present; // prefix-1 objects, eligible to be stored
Batch recent; // prefix-1 objects in the "future" key space
std::vector<uint256> missing; // prefix-2 keys that are never stored
std::vector<std::size_t> shuffle; // [0, poolSize) permutation for random-like access
std::size_t avgPayload = 0; // mean getData().size() over `present`
};
struct SetupContext
{
RunState& rs;
Database& db;
std::size_t poolSize;
};
struct IterateContext
{
RunState& rs;
Database& db;
std::uint32_t seq;
std::size_t index;
std::size_t poolSize;
};
struct Workload
{
std::string_view name;
std::function<void(SetupContext const&)> setup;
std::function<void(IterateContext const&)> iterate;
bool reportBytes = false;
bool pinIterations = false;
};
void
prepopulate(Database& db, Batch const& objects)
{
auto const seq = db.earliestLedgerSeq();
for (auto const& obj : objects)
{
Blob data(obj->getData());
db.store(obj->getType(), std::move(data), obj->getHash(), seq);
}
db.sync();
}
// One store() per iteration; a fresh Blob copy is handed over each time.
Workload const kStore{
.name = "Store",
.setup =
[](SetupContext const& ctx) {
ctx.rs.present = makePool(1, ctx.poolSize);
ctx.rs.avgPayload = averagePayload(ctx.rs.present);
},
.iterate =
[](IterateContext const& ctx) {
auto& [rs, db, seq, index, poolSize] = ctx;
auto const& obj = rs.present[index % poolSize];
Blob data(obj->getData());
db.store(obj->getType(), std::move(data), obj->getHash(), seq);
},
.reportBytes = true,
.pinIterations = true,
};
// One fetchNodeObject() of a stored key (a hit) per iteration.
Workload const kFetch{
.name = "Fetch",
.setup =
[](SetupContext const& ctx) {
ctx.rs.present = makePool(1, ctx.poolSize);
ctx.rs.avgPayload = averagePayload(ctx.rs.present);
prepopulate(ctx.db, ctx.rs.present);
},
.iterate =
[](IterateContext const& ctx) {
auto& [rs, db, seq, index, poolSize] = ctx;
auto obj = db.fetchNodeObject(rs.present[index % poolSize]->getHash(), seq);
benchmark::DoNotOptimize(obj);
},
.reportBytes = true,
};
// One fetchNodeObject() of a never-stored key (a miss) per iteration.
Workload const kMissing{
.name = "Missing",
.setup = [](SetupContext const& ctx) { ctx.rs.missing = makeMissingKeys(ctx.poolSize); },
.iterate =
[](IterateContext const& ctx) {
auto& [rs, db, seq, index, poolSize] = ctx;
auto obj = db.fetchNodeObject(rs.missing[index % poolSize], seq);
benchmark::DoNotOptimize(obj);
},
};
// 80% hits / 20% misses. The fetch index comes from a shuffle table so access
// is random-like without per-iteration RNG cost; sequential `index % poolSize`
// would be artificially cache-friendly.
Workload const kMixed{
.name = "Mixed",
.setup =
[](SetupContext const& ctx) {
ctx.rs.present = makePool(1, ctx.poolSize);
ctx.rs.missing = makeMissingKeys(ctx.poolSize);
ctx.rs.shuffle = makeShuffle(ctx.poolSize, /*seed=*/1);
prepopulate(ctx.db, ctx.rs.present);
},
.iterate =
[](IterateContext const& ctx) {
auto& [rs, db, seq, index, poolSize] = ctx;
auto const pick = rs.shuffle[index % poolSize];
std::shared_ptr<NodeObject> obj;
if (index % 5 == 0)
{
obj = db.fetchNodeObject(rs.missing[pick], seq);
}
else
{
obj = db.fetchNodeObject(rs.present[pick]->getHash(), seq);
}
benchmark::DoNotOptimize(obj);
},
};
// An xrpld-like cycle: a hit, a maybe-miss recent fetch, and a store. The
// recent fetch uses the shuffle table (not `slot`) so it doesn't fetch the item
// it's about to store this iteration - which would give an all-miss-then-hit
// step instead of a smooth ramp. The store walks sequentially so each recent
// object is stored once.
Workload const kWork{
.name = "Work",
.setup =
[](SetupContext const& ctx) {
ctx.rs.present = makePool(1, ctx.poolSize);
ctx.rs.recent = makePool(1, ctx.poolSize, ctx.poolSize);
ctx.rs.shuffle = makeShuffle(ctx.poolSize, /*seed=*/2);
prepopulate(ctx.db, ctx.rs.present);
},
.iterate =
[](IterateContext const& ctx) {
auto& [rs, db, seq, index, poolSize] = ctx;
auto const slot = index % poolSize;
auto const pick = rs.shuffle[slot];
auto historical = db.fetchNodeObject(rs.present[pick]->getHash(), seq);
benchmark::DoNotOptimize(historical);
auto recent = db.fetchNodeObject(rs.recent[pick]->getHash(), seq);
benchmark::DoNotOptimize(recent);
auto const& obj = rs.recent[slot];
Blob data(obj->getData());
db.store(obj->getType(), std::move(data), obj->getHash(), seq);
},
.pinIterations = true,
};
void
registerWorkload(BackendConfig const& bc, Workload const& w)
{
auto rs = std::make_shared<RunState>();
std::string const cfg = bc.config;
std::string name{kNamePrefix};
name += w.name;
name += kNameSeparator;
name += bc.name;
auto* b = benchmark::RegisterBenchmark(name, [rs, cfg, w](benchmark::State& state) {
auto const poolSize = static_cast<std::size_t>(state.range(0));
rs->harness = std::make_unique<DatabaseHarness>(cfg, kReadThreads);
auto& db = *rs->harness->db;
w.setup(SetupContext{.rs = *rs, .db = db, .poolSize = poolSize});
auto const seq = db.earliestLedgerSeq();
std::size_t index = 0;
for (auto _ : state)
{
w.iterate(
IterateContext{
.rs = *rs, .db = db, .seq = seq, .index = index, .poolSize = poolSize});
++index;
}
benchmark::ClobberMemory();
state.SetItemsProcessed(state.iterations());
if (w.reportBytes)
{
state.SetBytesProcessed(static_cast<std::int64_t>(state.iterations() * rs->avgPayload));
}
rs->harness.reset();
});
b->Arg(kDefaultPoolSize);
if (w.pinIterations)
b->Iterations(kDefaultPoolSize);
}
[[maybe_unused]] bool const kRegistered = [] {
auto const workloads = std::to_array({&kStore, &kFetch, &kMissing, &kMixed, &kWork});
for (auto const& bc : backendConfigs())
{
for (auto const* w : workloads)
registerWorkload(bc, *w);
}
return true;
}();
} // namespace
} // namespace xrpl::NodeStore

View File

@@ -0,0 +1,318 @@
#pragma once
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/temp_dir.h>
#include <xrpl/beast/xor_shift_engine.h>
#include <xrpl/config/BasicConfig.h>
#include <xrpl/nodestore/Backend.h>
#include <xrpl/nodestore/Database.h>
#include <xrpl/nodestore/DummyScheduler.h>
#include <xrpl/nodestore/Manager.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/nodestore/Scheduler.h>
#include <xrpl/nodestore/Types.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <numeric>
#include <random>
#include <string>
#include <utility>
#include <vector>
// Shared helpers for the NodeStore benchmarks.
//
namespace xrpl::NodeStore {
// Fill `bytes` of memory at `buffer` with random bits drawn from `g`.
template <class Generator>
inline void
rngcpy(void* buffer, std::size_t bytes, Generator& g)
{
using result_type = typename Generator::result_type;
while (bytes >= sizeof(result_type))
{
auto const v = g();
std::memcpy(buffer, &v, sizeof(v));
buffer = reinterpret_cast<std::uint8_t*>(buffer) + sizeof(v);
bytes -= sizeof(v);
}
if (bytes > 0)
{
auto const v = g();
std::memcpy(buffer, &v, bytes);
}
}
/**
* @brief Deterministic generator of a reproducible sequence of random NodeObjects.
*
* Indexing is stable: `obj(n)` and `key(n)` always return the same value for a
* given `n`, regardless of call order, because the engine is reseeded from `n`
* on every call.
*
* Using different prefixes guarantees the two key spaces are disjoint for the fetch-miss
* workloads.
*/
class Sequence
{
private:
static constexpr auto kMinSize = 250;
static constexpr auto kMaxSize = 1250;
beast::xor_shift_engine gen_;
std::uint8_t prefix_;
std::discrete_distribution<std::uint32_t> dType_;
std::uniform_int_distribution<std::uint32_t> dSize_;
public:
explicit Sequence(std::uint8_t prefix)
: prefix_(prefix)
// uniform distribution over hotLEDGER - hotTRANSACTION_NODE
// but exclude hotTRANSACTION = 2 (removed)
, dType_({1, 1, 0, 1, 1})
, dSize_(kMinSize, kMaxSize)
{
}
// Returns the n-th key. Used to generate keys that are never stored.
// The layout mirrors obj()'s: prefix at byte 0, RNG over the rest, so the
// two key spaces stay disjoint by construction (not by coincidence).
uint256
key(std::size_t n)
{
gen_.seed(n + 1);
uint256 result;
auto const data = static_cast<std::uint8_t*>(&*result.begin());
*data = prefix_;
rngcpy(data + 1, result.size() - 1, gen_);
return result;
}
// Returns the n-th complete NodeObject.
std::shared_ptr<NodeObject>
obj(std::size_t n)
{
gen_.seed(n + 1);
uint256 key;
auto const data = static_cast<std::uint8_t*>(&*key.begin());
*data = prefix_;
rngcpy(data + 1, key.size() - 1, gen_);
Blob value(dSize_(gen_));
rngcpy(&value[0], value.size(), gen_);
return NodeObject::createObject(
safeCast<NodeObjectType>(dType_(gen_)), std::move(value), key);
}
// Fills `b` with `size` consecutive NodeObjects starting at index `n`.
void
batch(std::size_t n, Batch& b, std::size_t size)
{
b.clear();
b.reserve(size);
while ((size--) != 0u)
b.push_back(obj(n++));
}
};
// Parse a comma-separated "key=value,key=value" string into a config Section.
inline Section
parseConfig(std::string const& s)
{
Section section;
std::vector<std::string> values;
boost::split(values, s, boost::algorithm::is_any_of(","));
section.append(values);
return section;
}
// Pre-generate `count` distinct objects from key space `prefix`, starting at
// sequence index `start`.
inline Batch
makePool(std::uint8_t prefix, std::size_t count, std::size_t start = 0)
{
Sequence seq(prefix);
Batch pool;
pool.reserve(count);
for (std::size_t i = 0; i < count; ++i)
pool.push_back(seq.obj(start + i));
return pool;
}
// Pre-generate `count` keys disjoint from every `makePool(...)` object, for
// measuring fetches that miss.
inline std::vector<uint256>
makeMissingKeys(std::size_t count)
{
Sequence seq(2);
std::vector<uint256> keys;
keys.reserve(count);
for (std::size_t i = 0; i < count; ++i)
keys.push_back(seq.key(i));
return keys;
}
// Mean payload size across a pool, used for SetBytesProcessed throughput.
inline std::size_t
averagePayload(Batch const& pool)
{
if (pool.empty())
return 0;
std::size_t total = 0;
for (auto const& obj : pool)
total += obj->getData().size();
return total / pool.size();
}
// Store every object and flush, so a following fetch exercises the real read
// path rather than an in-memory write buffer.
//
// We chunk the write at kBatchWriteLimitSize because Types.h documents that as
// the maximum allowed batch size. NuDB happens to tolerate larger batches
// today, but the benchmark should not rely on that.
//
// sync() is a no-op for both NuDB and RocksDB at the moment (NuDB has a small
// internal burst buffer that the timed loop will warm up). That is a contract
// hint, not a guarantee; if either backend ever grows a real flush we get it
// here for free.
inline void
prepopulate(Backend& backend, Batch const& objects)
{
for (std::size_t i = 0; i < objects.size(); i += kBatchWriteLimitSize)
{
auto const end = std::min(i + kBatchWriteLimitSize, objects.size());
backend.storeBatch(Batch(objects.begin() + i, objects.begin() + end));
}
backend.sync();
}
// A deterministic permutation of [0, size). Lets the timed loop visit the
// pre-generated pool in a random-like order with zero RNG cost per iteration -
// the Timing_test workloads it replaces used uniform_int_distribution per
// fetch, and a shuffle table reproduces that access pattern without paying for
// the distribution inside the timed region.
inline std::vector<std::size_t>
makeShuffle(std::size_t size, std::uint64_t seed)
{
std::vector<std::size_t> v(size);
std::iota(v.begin(), v.end(), std::size_t{0});
beast::xor_shift_engine gen(seed);
std::shuffle(v.begin(), v.end(), gen);
return v;
}
// Partition a pool into fixed-size batches. Any trailing remainder shorter than
// `batchSize` is dropped, so every returned batch has exactly `batchSize`.
inline std::vector<Batch>
sliceBatches(Batch const& pool, std::size_t batchSize)
{
std::vector<Batch> batches;
if (batchSize == 0)
return batches;
batches.reserve(pool.size() / batchSize);
for (std::size_t i = 0; i + batchSize <= pool.size(); i += batchSize)
batches.emplace_back(pool.begin() + i, pool.begin() + i + batchSize);
return batches;
}
/**
* @brief RAII owner of a NodeStore Backend opened on a private temporary directory.
*
* Member declaration order matters: `tempDir` is declared first so it is
* destroyed last, after the backend has closed and released its files.
*/
struct BackendHarness
{
beast::TempDir tempDir;
DummyScheduler scheduler;
beast::Journal journal{beast::Journal::getNullSink()};
std::unique_ptr<Backend> backend;
explicit BackendHarness(std::string const& configString)
{
Section config = parseConfig(configString);
// A private, unique path per harness, so concurrent or repeated runs
// never share on-disk state.
config.set("path", tempDir.path());
backend =
Manager::instance().makeBackend(config, megabytes(std::size_t{4}), scheduler, journal);
backend->setDeletePath();
backend->open();
}
~BackendHarness()
{
if (backend)
backend->close();
}
};
/**
* RAII owner of a NodeStore Database - the application-facing wrapper around a
* Backend, which adds fetch/store accounting and the async read-thread pool.
*/
struct DatabaseHarness
{
beast::TempDir tempDir;
DummyScheduler scheduler;
beast::Journal journal{beast::Journal::getNullSink()};
std::unique_ptr<Database> db;
DatabaseHarness(std::string const& configString, int readThreads)
{
Section config = parseConfig(configString);
config.set("path", tempDir.path());
db = Manager::instance().makeDatabase(
megabytes(std::size_t{4}), scheduler, readThreads, config, journal);
}
~DatabaseHarness()
{
if (db)
db->stop();
}
};
// A NodeStore backend to benchmark, named for the --benchmark_filter CLI flag.
struct BackendConfig
{
char const* name; // short label, e.g. "nudb"
char const* config; // parseConfig() string, e.g. "type=nudb"
};
// The backends every workload is registered against.
//
// The in-memory backend is intentionally excluded. It keeps its table in a
// process-global map keyed by path, with no removal API, so building a fresh
// backend per run - as a microbenchmark must - would leak the whole dataset on
// every run. Timing_test, the suite this benchmark replaces, excluded it for
// the same reason. NuDB and RocksDB are the production backends worth timing.
//
// RocksDB is included only when it was compiled in (xrpl.libxrpl carries
// XRPL_ROCKSDB_AVAILABLE transitively).
inline std::vector<BackendConfig> const&
backendConfigs()
{
static std::vector<BackendConfig> const kConfigs = {
{.name = "nudb", .config = "type=nudb"},
#if XRPL_ROCKSDB_AVAILABLE
{.name = "rocksdb",
.config = "type=rocksdb,open_files=2000,filter_bits=12,cache_mb=256,"
"file_size_mb=8,file_size_mult=2"},
#endif
};
return kConfigs;
}
} // namespace xrpl::NodeStore

View File

@@ -11,6 +11,7 @@
#include <xrpl/beast/insight/Meter.h>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <utility>
@@ -87,6 +88,7 @@ public:
Collector::ptr collector;
Items items;
mutable std::mutex groupMutex;
explicit GroupsImp(Collector::ptr collector) : collector(std::move(collector))
{
@@ -97,6 +99,7 @@ public:
Group::ptr const&
get(std::string const& name) override
{
std::scoped_lock const _{groupMutex};
std::pair<Items::iterator, bool> const result(items.emplace(name, Group::ptr()));
Group::ptr& group(result.first->second);
if (result.second)

View File

@@ -113,6 +113,8 @@ ApplyStateTable::apply(
TER ter,
std::optional<STAmount> const& deliver,
std::optional<uint256 const> const& parentBatchId,
std::optional<std::uint32_t> const& gasUsed,
std::optional<std::int32_t> const& vmReturnCode,
bool isDryRun,
beast::Journal j)
{
@@ -127,6 +129,8 @@ ApplyStateTable::apply(
meta.setDeliveredAmount(deliver);
meta.setParentBatchID(parentBatchId);
meta.setGasUsed(gasUsed);
meta.setVMReturnCode(vmReturnCode);
Mods newMod;
for (auto& item : items_)

View File

@@ -30,7 +30,7 @@ ApplyViewImpl::apply(
bool isDryRun,
beast::Journal j)
{
return items_.apply(to, tx, ter, deliver_, parentBatchId, isDryRun, j);
return items_.apply(to, tx, ter, deliver_, parentBatchId, gasUsed_, vmReturnCode_, isDryRun, j);
}
std::size_t

View File

@@ -198,6 +198,12 @@ Ledger::Ledger(
sle->at(sfReserveIncrement) = *f;
sle->at(sfReferenceFeeUnits) = kFeeUnitsDeprecated;
}
if (std::ranges::find(amendments, featureSmartEscrow) != amendments.end())
{
sle->at(sfGasLimit) = fees.gasLimit;
sle->at(sfBytecodeSizeLimit) = fees.bytecodeSizeLimit;
sle->at(sfGasPrice) = fees.gasPrice;
}
rawInsert(sle);
}
@@ -564,6 +570,7 @@ Ledger::setup()
{
bool oldFees = false;
bool newFees = false;
bool extensionFees = false;
{
auto const baseFee = sle->at(~sfBaseFee);
auto const reserveBase = sle->at(~sfReserveBase);
@@ -580,6 +587,7 @@ Ledger::setup()
auto const baseFeeXRP = sle->at(~sfBaseFeeDrops);
auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops);
auto const reserveIncrementXRP = sle->at(~sfReserveIncrementDrops);
auto assign = [&ret](XRPAmount& dest, std::optional<STAmount> const& src) {
if (src)
{
@@ -598,6 +606,22 @@ Ledger::setup()
assign(fees_.increment, reserveIncrementXRP);
newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP;
}
{
auto const gasLimit = sle->at(~sfGasLimit);
auto const bytecodeSizeLimit = sle->at(~sfBytecodeSizeLimit);
auto const gasPrice = sle->at(~sfGasPrice);
auto assign = [](std::uint32_t& dest, std::optional<std::uint32_t> const& src) {
if (src)
{
dest = src.value();
}
};
assign(fees_.gasLimit, gasLimit);
assign(fees_.bytecodeSizeLimit, bytecodeSizeLimit);
assign(fees_.gasPrice, gasPrice);
extensionFees = gasLimit || bytecodeSizeLimit || gasPrice;
}
if (oldFees && newFees)
{
// Should be all of one or the other, but not both
@@ -608,6 +632,12 @@ Ledger::setup()
// Can't populate the new fees before the amendment is enabled
ret = false;
}
if (!rules_.enabled(featureSmartEscrow) && extensionFees)
{
// Can't populate the extension fees before the amendment is
// enabled
ret = false;
}
}
}
catch (SHAMapMissingNode const&)

View File

@@ -312,6 +312,25 @@ computeInterestAndFeeParts(
return std::make_pair(interest - fee, fee);
}
/* Rounds a raw (unrounded) interest amount to the loan's scale, then splits
* the rounded amount into net interest (to the vault) and management fee (to
* the broker).
*
* This is the common "round then split" step shared by late payment, full
* payment, and overpayment interest calculations.
*/
std::pair<Number, Number>
roundAndSplitInterest(
Asset const& asset,
Number const& rawInterest,
TenthBips16 managementFeeRate,
std::int32_t loanScale,
Number::RoundingMode mode = Number::getround())
{
auto const interest = roundToAsset(asset, rawInterest, loanScale, mode);
return computeInterestAndFeeParts(asset, interest, managementFeeRate, loanScale);
}
/* Calculates penalty interest accrued on overdue payments.
* Returns 0 if payment is not late.
*
@@ -387,22 +406,18 @@ loanAccruedInterest(
*
* This is the core function that updates the Loan ledger object fields based on
* a computed payment.
* The function is templated to work with both direct Number/uint32_t values
* (for testing/simulation) and ValueProxy types (for actual ledger updates).
*/
template <class NumberProxy, class UInt32Proxy, class UInt32OptionalProxy>
LoanPaymentParts
doPayment(
ExtendedPaymentComponents const& payment,
NumberProxy& totalValueOutstandingProxy,
NumberProxy& principalOutstandingProxy,
NumberProxy& managementFeeOutstandingProxy,
UInt32Proxy& paymentRemainingProxy,
UInt32Proxy& prevPaymentDateProxy,
UInt32OptionalProxy& nextDueDateProxy,
std::uint32_t paymentInterval)
doPayment(ExtendedPaymentComponents const& payment, SLE::ref loan)
{
auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
auto managementFeeOutstandingProxy = loan->at(sfManagementFeeOutstanding);
auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDueDate);
auto nextDueDateProxy = loan->at(sfNextPaymentDueDate);
std::uint32_t const paymentInterval = loan->at(sfPaymentInterval);
XRPL_ASSERT_PARTS(nextDueDateProxy, "xrpl::detail::doPayment", "Next due date proxy set");
if (payment.specialCase == PaymentSpecialCase::Final)
@@ -470,16 +485,12 @@ doPayment(
// Principal can never exceed total value (principal is part of total value)
XRPL_ASSERT_PARTS(
// Use an explicit cast because the template parameter can be
// ValueProxy<Number> or Number
static_cast<Number>(principalOutstandingProxy) <=
static_cast<Number>(totalValueOutstandingProxy),
"xrpl::detail::doPayment",
"principal does not exceed total");
XRPL_ASSERT_PARTS(
// Use an explicit cast because the template parameter can be
// ValueProxy<Number> or Number
static_cast<Number>(managementFeeOutstandingProxy) >= beast::kZero,
"xrpl::detail::doPayment",
"fee outstanding stays valid");
@@ -717,22 +728,23 @@ tryOverpayment(
* overpayment would leave the loan in an invalid state, we can reject it
* gracefully without corrupting the ledger data.
*/
template <class NumberProxy>
std::expected<LoanPaymentParts, TER>
doOverpayment(
Rules const& rules,
Asset const& asset,
std::int32_t loanScale,
ExtendedPaymentComponents const& overpaymentComponents,
NumberProxy& totalValueOutstandingProxy,
NumberProxy& principalOutstandingProxy,
NumberProxy& managementFeeOutstandingProxy,
NumberProxy& periodicPaymentProxy,
SLE::ref loan,
Number const& periodicRate,
std::uint32_t const paymentRemaining,
TenthBips16 const managementFeeRate,
beast::Journal j)
{
auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
auto managementFeeOutstandingProxy = loan->at(sfManagementFeeOutstanding);
auto periodicPaymentProxy = loan->at(sfPeriodicPayment);
auto const paymentsRemaining = loan->at(sfPaymentRemaining);
auto const loanState = constructLoanState(
totalValueOutstandingProxy, principalOutstandingProxy, managementFeeOutstandingProxy);
auto const periodicPayment = periodicPaymentProxy;
@@ -744,7 +756,7 @@ doOverpayment(
<< ", interestPart: " << overpaymentComponents.trackedInterestPart()
<< ", untrackedInterest: " << overpaymentComponents.untrackedInterest
<< ", totalDue: " << overpaymentComponents.totalDue
<< ", payments remaining :" << paymentRemaining;
<< ", payments remaining :" << paymentsRemaining;
// Attempt to re-amortize the loan with the overpayment applied.
// This modifies the temporary copies, leaving the proxies unchanged.
@@ -756,7 +768,7 @@ doOverpayment(
loanState,
periodicPayment,
periodicRate,
paymentRemaining,
paymentsRemaining,
managementFeeRate,
j);
if (!ret)
@@ -864,16 +876,15 @@ std::expected<ExtendedPaymentComponents, TER>
computeLatePayment(
Asset const& asset,
ApplyView const& view,
Number const& principalOutstanding,
std::int32_t nextDueDate,
SLE::const_ref loan,
ExtendedPaymentComponents const& periodic,
TenthBips32 lateInterestRate,
std::int32_t loanScale,
Number const& latePaymentFee,
STAmount const& amount,
TenthBips16 managementFeeRate,
beast::Journal j)
{
std::int32_t const nextDueDate = loan->at(sfNextPaymentDueDate);
std::int32_t const loanScale = loan->at(sfLoanScale);
// Check if the due date has passed. If not, reject the payment as
// being too soon
if (!hasExpired(view, nextDueDate))
@@ -881,15 +892,15 @@ computeLatePayment(
// Calculate the penalty interest based on how long the payment is overdue.
auto const latePaymentInterest = loanLatePaymentInterest(
principalOutstanding, lateInterestRate, view.parentCloseTime(), nextDueDate);
loan->at(sfPrincipalOutstanding),
TenthBips32{loan->at(sfLateInterestRate)},
view.parentCloseTime(),
nextDueDate);
// Round the late interest and split it between the vault (net interest)
// and the broker (management fee portion). This lambda ensures we
// round before splitting to maintain precision.
auto const [roundedLateInterest, roundedLateManagementFee] = [&]() {
auto const interest = roundToAsset(asset, latePaymentInterest, loanScale);
return computeInterestAndFeeParts(asset, interest, managementFeeRate, loanScale);
}();
// and the broker (management fee portion).
auto const [roundedLateInterest, roundedLateManagementFee] =
roundAndSplitInterest(asset, latePaymentInterest, managementFeeRate, loanScale);
XRPL_ASSERT(roundedLateInterest >= 0, "xrpl::detail::computeLatePayment : valid late interest");
XRPL_ASSERT_PARTS(
@@ -908,7 +919,7 @@ computeLatePayment(
// 1. Regular service fee (from periodic.untrackedManagementFee)
// 2. Late payment fee (fixed penalty)
// 3. Management fee portion of late interest
periodic.untrackedManagementFee + latePaymentFee + roundedLateManagementFee,
periodic.untrackedManagementFee + loan->at(sfLatePaymentFee) + roundedLateManagementFee,
// Untracked interest includes:
// 1. Any untracked interest from the regular payment (usually 0)
@@ -958,22 +969,15 @@ std::expected<ExtendedPaymentComponents, TER>
computeFullPayment(
Asset const& asset,
ApplyView& view,
Number const& principalOutstanding,
Number const& managementFeeOutstanding,
Number const& periodicPayment,
std::uint32_t paymentRemaining,
std::uint32_t prevPaymentDate,
std::uint32_t const startDate,
std::uint32_t const paymentInterval,
TenthBips32 const closeInterestRate,
std::int32_t loanScale,
Number const& totalInterestOutstanding,
SLE::const_ref loan,
Number const& periodicRate,
Number const& closePaymentFee,
STAmount const& amount,
TenthBips16 managementFeeRate,
beast::Journal j)
{
std::uint32_t const paymentRemaining = loan->at(sfPaymentRemaining);
std::int32_t const loanScale = loan->at(sfLoanScale);
// Full payment must be made before the final scheduled payment.
if (paymentRemaining <= 1)
{
@@ -986,7 +990,7 @@ computeFullPayment(
// This theoretical (unrounded) value is used to compute interest and
// penalties accurately.
Number const theoreticalPrincipalOutstanding = loanPrincipalFromPeriodicPayment(
view.rules(), periodicPayment, periodicRate, paymentRemaining);
view.rules(), loan->at(sfPeriodicPayment), periodicRate, paymentRemaining);
// Full payment interest includes both accrued interest (time since last
// payment) and prepayment penalty (for closing early).
@@ -994,18 +998,21 @@ computeFullPayment(
theoreticalPrincipalOutstanding,
periodicRate,
view.parentCloseTime(),
paymentInterval,
prevPaymentDate,
startDate,
closeInterestRate);
loan->at(sfPaymentInterval),
loan->at(sfPreviousPaymentDueDate),
loan->at(sfStartDate),
TenthBips32{loan->at(sfCloseInterestRate)});
// Split the full payment interest into net interest (to vault) and
// management fee (to broker), applying proper rounding.
auto const [roundedFullInterest, roundedFullManagementFee] = [&]() {
auto const interest =
roundToAsset(asset, fullPaymentInterest, loanScale, Number::RoundingMode::Downward);
return computeInterestAndFeeParts(asset, interest, managementFeeRate, loanScale);
}();
// Split the full payment interest into net interest (to vault) and management fee (to broker),
// applying proper rounding.
auto const [roundedFullInterest, roundedFullManagementFee] = roundAndSplitInterest(
asset, fullPaymentInterest, managementFeeRate, loanScale, Number::RoundingMode::Downward);
LoanState const loanState = constructLoanState(loan);
Number const principalOutstanding = loanState.principalOutstanding;
Number const managementFeeOutstanding = loanState.managementFeeDue;
Number const totalInterestOutstanding = loanState.interestDue;
Number const closePaymentFee = roundToAsset(asset, loan->at(sfClosePaymentFee), loanScale);
ExtendedPaymentComponents const full{
PaymentComponents{
@@ -1046,8 +1053,7 @@ computeFullPayment(
"xrpl::detail::computeFullPayment",
"total due is rounded");
JLOG(j.trace()) << "computeFullPayment result: periodicPayment: " << periodicPayment
<< ", periodicRate: " << periodicRate
JLOG(j.trace()) << "computeFullPayment result: periodicRate: " << periodicRate
<< ", paymentRemaining: " << paymentRemaining
<< ", theoreticalPrincipalOutstanding: " << theoreticalPrincipalOutstanding
<< ", fullPaymentInterest: " << fullPaymentInterest
@@ -1298,6 +1304,34 @@ computePaymentComponents(
};
}
/* Thin overload of computePaymentComponents() that unwraps the tracked
* fields directly from the Loan ledger object. `periodicRate` is derived
* rather than stored, and `managementFeeRate` comes from the LoanBroker, not
* the Loan, so both remain explicit parameters. Kept separate from the
* value-based overload above, which is exercised directly by unit tests
* against simulated (non-ledger) loan states.
*/
PaymentComponents
computePaymentComponents(
Rules const& rules,
Asset const& asset,
SLE::ref loan,
Number const& periodicRate,
TenthBips16 managementFeeRate)
{
return computePaymentComponents(
rules,
asset,
loan->at(sfLoanScale),
loan->at(sfTotalValueOutstanding),
loan->at(sfPrincipalOutstanding),
loan->at(sfManagementFeeOutstanding),
loan->at(sfPeriodicPayment),
periodicRate,
loan->at(sfPaymentRemaining),
managementFeeRate);
}
/* Computes payment components for an overpayment scenario.
*
* An overpayment occurs when a borrower pays more than the scheduled periodic
@@ -1342,11 +1376,12 @@ computeOverpaymentComponents(
// This interest doesn't follow the normal amortization schedule - it's
// a one-time charge for paying early.
// Equation (20) and (21) from XLS-66 spec, Section A-2 Equation Glossary
auto const [roundedOverpaymentInterest, roundedOverpaymentManagementFee] = [&]() {
auto const interest =
roundToAsset(asset, tenthBipsOfValue(overpayment, overpaymentInterestRate), loanScale);
return detail::computeInterestAndFeeParts(asset, interest, managementFeeRate, loanScale);
}();
auto const [roundedOverpaymentInterest, roundedOverpaymentManagementFee] =
roundAndSplitInterest(
asset,
tenthBipsOfValue(overpayment, overpaymentInterestRate),
managementFeeRate,
loanScale);
auto const result = detail::ExtendedPaymentComponents{
// Build the payment components, after fees and penalty
@@ -1373,6 +1408,265 @@ computeOverpaymentComponents(
return result;
}
/* Derives the two rate values every make*Payment() helper needs: the
* broker's management fee rate, and the loan's periodic (per-payment-period)
* interest rate.
*/
std::pair<TenthBips16, Number>
loanRatesFor(SLE::const_ref loan, SLE::const_ref brokerSle)
{
TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
TenthBips32 const interestRate{loan->at(sfInterestRate)};
Number const periodicRate = loanPeriodicRate(interestRate, loan->at(sfPaymentInterval));
XRPL_ASSERT(interestRate == 0 || periodicRate > 0, "xrpl::detail::loanRatesFor : valid rate");
return {managementFeeRate, periodicRate};
}
/* Handles a full (early payoff) payment. Implements the "full payment"
* branch of the make_payment function from the XLS-66 spec, Section
* 3.2.4.4.
*/
std::expected<LoanPaymentParts, TER>
makeFullPayment(
Asset const& asset,
ApplyView& view,
SLE::ref loan,
SLE::const_ref brokerSle,
STAmount const& amount,
beast::Journal j)
{
auto const [managementFeeRate, periodicRate] = loanRatesFor(loan, brokerSle);
auto const fullPaymentComponents =
computeFullPayment(asset, view, loan, periodicRate, amount, managementFeeRate, j);
// computeFullPayment only ever fails with a genuine error TER (never
// tesSUCCESS), so there is no separate "no-op" outcome to handle here.
if (fullPaymentComponents.has_value())
return doPayment(*fullPaymentComponents, loan);
return std::unexpected(fullPaymentComponents.error());
}
/* Handles a late payment (past due date, with the late-payment flag set).
* Implements the "late payment" branch of the make_payment function from
* the XLS-66 spec, Section 3.2.4.4.
*/
std::expected<LoanPaymentParts, TER>
makeLatePayment(
Asset const& asset,
ApplyView const& view,
SLE::ref loan,
SLE::const_ref brokerSle,
STAmount const& amount,
beast::Journal j)
{
auto const [managementFeeRate, periodicRate] = loanRatesFor(loan, brokerSle);
Number const serviceFee = loan->at(sfLoanServiceFee);
ExtendedPaymentComponents const periodic{
computePaymentComponents(view.rules(), asset, loan, periodicRate, managementFeeRate),
serviceFee};
XRPL_ASSERT_PARTS(
periodic.trackedPrincipalDelta >= 0,
"xrpl::detail::makeLatePayment",
"regular payment valid principal");
auto const latePaymentComponents =
computeLatePayment(asset, view, loan, periodic, amount, managementFeeRate, j);
// computeLatePayment only ever fails with a genuine error TER (never
// tesSUCCESS), so there is no separate "no-op" outcome to handle here.
if (latePaymentComponents.has_value())
return doPayment(*latePaymentComponents, loan);
return std::unexpected(latePaymentComponents.error());
}
/* Handles regular scheduled payments, including an optional overpayment tail.
* Implements the "regular" and "overpayment" branches of the make_payment
* function from the XLS-66 spec, Section 3.2.4.4.
*/
std::expected<LoanPaymentParts, TER>
makeRegularPayment(
Asset const& asset,
ApplyView const& view,
SLE::ref loan,
SLE::const_ref brokerSle,
STAmount const& amount,
LoanPaymentType const paymentType,
beast::Journal j)
{
using namespace Lending;
XRPL_ASSERT_PARTS(
paymentType == LoanPaymentType::Regular || paymentType == LoanPaymentType::Overpayment,
"xrpl::detail::makeRegularPayment",
"regular payment type");
auto const [managementFeeRate, periodicRate] = loanRatesFor(loan, brokerSle);
std::int32_t const loanScale = loan->at(sfLoanScale);
Number const serviceFee = loan->at(sfLoanServiceFee);
ExtendedPaymentComponents periodic{
computePaymentComponents(view.rules(), asset, loan, periodicRate, managementFeeRate),
serviceFee};
XRPL_ASSERT_PARTS(
periodic.trackedPrincipalDelta >= 0,
"xrpl::detail::makeRegularPayment",
"regular payment valid principal");
// Keep a running total of the actual parts paid
LoanPaymentParts totalParts;
Number totalPaid = kNumZero;
std::size_t numPayments = 0;
// Cached here (rather than re-looking up loan->at(sfPaymentRemaining) at each use) since it's
// read multiple times below. It's a write-through proxy, so it still reflects doPayment's
// mutations each iteration.
auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
while ((amount >= (totalPaid + periodic.totalDue)) && paymentRemainingProxy > 0 &&
numPayments < kLoanMaximumPaymentsPerTransaction)
{
// Try to make more payments
XRPL_ASSERT_PARTS(
periodic.trackedPrincipalDelta >= 0,
"xrpl::detail::makeRegularPayment",
"payment pays non-negative principal");
totalPaid += periodic.totalDue;
totalParts += doPayment(periodic, loan);
++numPayments;
XRPL_ASSERT_PARTS(
(periodic.specialCase == PaymentSpecialCase::Final) == (paymentRemainingProxy == 0),
"xrpl::detail::makeRegularPayment",
"final payment is the final payment");
// Don't compute the next payment if this was the last payment
if (periodic.specialCase == PaymentSpecialCase::Final)
break;
periodic = ExtendedPaymentComponents{
computePaymentComponents(view.rules(), asset, loan, periodicRate, managementFeeRate),
serviceFee};
}
if (numPayments == 0)
{
JLOG(j.warn()) << "Regular loan payment amount is insufficient. Due: " << periodic.totalDue
<< ", paid: " << amount;
return std::unexpected(tecINSUFFICIENT_PAYMENT);
}
XRPL_ASSERT_PARTS(
totalParts.principalPaid + totalParts.interestPaid + totalParts.feePaid == totalPaid,
"xrpl::detail::makeRegularPayment",
"payment parts add up");
XRPL_ASSERT_PARTS(
totalParts.valueChange == 0, "xrpl::detail::makeRegularPayment", "no value change");
// -------------------------------------------------------------
// overpayment handling
//
// If the "fixCleanup3_1_3" amendment is enabled, truncate "amount",
// at the loan scale. If the raw value is used, the overpayment
// amount could be meaningless dust. Trying to process such a small
// amount will, at best, waste time when all the result values round
// to zero. At worst, it can cause logical errors with tiny amounts
// of interest that don't add up correctly.
auto const roundedAmount = view.rules().enabled(fixCleanup3_1_3)
? roundToAsset(asset, amount, loanScale, Number::RoundingMode::TowardsZero)
: amount;
bool const overpaymentSupported =
paymentType == LoanPaymentType::Overpayment && loan->isFlag(lsfLoanOverpayment);
bool const overpaymentAllowed = //
paymentRemainingProxy > 0 && //
totalPaid < roundedAmount && //
numPayments < kLoanMaximumPaymentsPerTransaction;
if (overpaymentSupported && overpaymentAllowed)
{
TenthBips32 const overpaymentInterestRate{loan->at(sfOverpaymentInterestRate)};
TenthBips32 const overpaymentFeeRate{loan->at(sfOverpaymentFee)};
// It shouldn't be possible for the overpayment to be greater than
// totalValueOutstanding, because that would have been processed as
// another normal payment. But cap it just in case.
Number const overpaymentRaw =
std::min(roundedAmount - totalPaid, *loan->at(sfTotalValueOutstanding));
bool const fixEnabled = view.rules().enabled(fixCleanup3_2_0);
Number const overpayment = fixEnabled
? roundToAsset(asset, overpaymentRaw, loanScale, Number::RoundingMode::Downward)
: overpaymentRaw;
// Post-amendment, the rounded overpayment can be zero; pre-amendment
// it's always positive given the surrounding guards.
if (!fixEnabled || overpayment > 0)
{
ExtendedPaymentComponents const overpaymentComponents = computeOverpaymentComponents(
view.rules(),
asset,
loanScale,
overpayment,
overpaymentInterestRate,
overpaymentFeeRate,
managementFeeRate);
// Don't process an overpayment if the whole amount (or more!)
// gets eaten by fees and interest.
if (overpaymentComponents.trackedPrincipalDelta > 0)
{
XRPL_ASSERT_PARTS(
overpaymentComponents.untrackedInterest >= beast::kZero,
"xrpl::detail::makeRegularPayment",
"overpayment penalty did not reduce value of loan");
if (auto const overResult = doOverpayment(
view.rules(),
asset,
loanScale,
overpaymentComponents,
loan,
periodicRate,
managementFeeRate,
j))
{
totalParts += *overResult;
}
else if (overResult.error())
{
// error() will be the TER returned if a payment is not
// made. It will only evaluate to true if it's unsuccessful.
// Otherwise, tesSUCCESS means nothing was done, so
// continue.
return std::unexpected(overResult.error());
}
}
}
}
// Check the final results are rounded, to double-check that the
// intermediate steps were rounded.
XRPL_ASSERT(
isRounded(asset, totalParts.principalPaid, loanScale) &&
totalParts.principalPaid >= beast::kZero,
"xrpl::detail::makeRegularPayment : total principal paid is valid");
XRPL_ASSERT(
isRounded(asset, totalParts.interestPaid, loanScale) &&
totalParts.interestPaid >= beast::kZero,
"xrpl::detail::makeRegularPayment : total interest paid is valid");
XRPL_ASSERT(
isRounded(asset, totalParts.valueChange, loanScale),
"xrpl::detail::makeRegularPayment : loan value change is valid");
XRPL_ASSERT(
isRounded(asset, totalParts.feePaid, loanScale) && totalParts.feePaid >= beast::kZero,
"xrpl::detail::makeRegularPayment : fee paid is valid");
return totalParts;
}
} // namespace detail
detail::LoanStateDeltas
@@ -1632,8 +1926,10 @@ constructLoanState(
}
LoanState
constructRoundedLoanState(SLE::const_ref loan)
constructLoanState(SLE::const_ref loan)
{
XRPL_ASSERT(loan && loan->getType() == ltLOAN, "xrpl::constructLoanState : valid loan SLE");
return constructLoanState(
loan->at(sfTotalValueOutstanding),
loan->at(sfPrincipalOutstanding),
@@ -1790,12 +2086,7 @@ loanMakePayment(
LoanPaymentType const paymentType,
beast::Journal j)
{
using namespace Lending;
auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
if (paymentRemainingProxy == 0 || principalOutstandingProxy == 0)
if (loan->at(sfPaymentRemaining) == 0 || loan->at(sfPrincipalOutstanding) == 0)
{
// Loan complete this is already checked in LoanPay::preclaim()
// LCOV_EXCL_START
@@ -1804,9 +2095,6 @@ loanMakePayment(
// LCOV_EXCL_STOP
}
auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
auto managementFeeOutstandingProxy = loan->at(sfManagementFeeOutstanding);
// Next payment due date must be set unless the loan is complete
auto nextDueDateProxy = loan->at(sfNextPaymentDueDate);
if (*nextDueDateProxy == 0)
@@ -1815,26 +2103,8 @@ loanMakePayment(
return std::unexpected(tecINTERNAL);
}
std::int32_t const loanScale = loan->at(sfLoanScale);
TenthBips32 const interestRate{loan->at(sfInterestRate)};
Number const serviceFee = loan->at(sfLoanServiceFee);
TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
Number const periodicPayment = loan->at(sfPeriodicPayment);
auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDueDate);
std::uint32_t const startDate = loan->at(sfStartDate);
std::uint32_t const paymentInterval = loan->at(sfPaymentInterval);
// Compute the periodic rate that will be used for calculations
// throughout
Number const periodicRate = loanPeriodicRate(interestRate, paymentInterval);
XRPL_ASSERT(interestRate == 0 || periodicRate > 0, "xrpl::loanMakePayment : valid rate");
XRPL_ASSERT(*totalValueOutstandingProxy > 0, "xrpl::loanMakePayment : valid total value");
XRPL_ASSERT(
*loan->at(sfTotalValueOutstanding) > 0, "xrpl::loanMakePayment : valid total value");
view.update(loan);
@@ -1844,311 +2114,29 @@ loanMakePayment(
{
// If the payment is late, and the late flag was not set, it's not
// valid
JLOG(j.warn()) << "Loan payment is overdue. Use the tfLoanLatePayment "
"transaction "
"flag to make a late payment. Loan was created on "
<< startDate << ", prev payment due date is " << prevPaymentDateProxy
<< ", next payment due date is " << nextDueDateProxy << ", ledger time is "
JLOG(j.warn()) << "Loan payment is overdue. Use the tfLoanLatePayment transaction flag to "
"make a late payment. Loan was created on "
<< loan->at(sfStartDate) << ", prev payment due date is "
<< loan->at(sfPreviousPaymentDueDate) << ", next payment due date is "
<< nextDueDateProxy << ", ledger time is "
<< view.parentCloseTime().time_since_epoch().count();
return std::unexpected(tecEXPIRED);
}
// -------------------------------------------------------------
// full payment handling
if (paymentType == LoanPaymentType::Full)
switch (paymentType)
{
TenthBips32 const closeInterestRate{loan->at(sfCloseInterestRate)};
Number const closePaymentFee = roundToAsset(asset, loan->at(sfClosePaymentFee), loanScale);
LoanState const roundedLoanState = constructLoanState(
totalValueOutstandingProxy, principalOutstandingProxy, managementFeeOutstandingProxy);
auto const fullPaymentComponents = detail::computeFullPayment(
asset,
view,
principalOutstandingProxy,
managementFeeOutstandingProxy,
periodicPayment,
paymentRemainingProxy,
prevPaymentDateProxy,
startDate,
paymentInterval,
closeInterestRate,
loanScale,
roundedLoanState.interestDue,
periodicRate,
closePaymentFee,
amount,
managementFeeRate,
j);
if (fullPaymentComponents.has_value())
{
return doPayment(
*fullPaymentComponents,
totalValueOutstandingProxy,
principalOutstandingProxy,
managementFeeOutstandingProxy,
paymentRemainingProxy,
prevPaymentDateProxy,
nextDueDateProxy,
paymentInterval);
}
if (fullPaymentComponents.error())
{
// error() will be the TER returned if a payment is not made. It
// will only evaluate to true if it's unsuccessful. Otherwise,
// tesSUCCESS means nothing was done, so continue.
return std::unexpected(fullPaymentComponents.error());
}
// LCOV_EXCL_START
UNREACHABLE("xrpl::loanMakePayment : invalid full payment result");
JLOG(j.error()) << "Full payment computation failed unexpectedly.";
return std::unexpected(tecINTERNAL);
// LCOV_EXCL_STOP
case LoanPaymentType::Full:
return detail::makeFullPayment(asset, view, loan, brokerSle, amount, j);
case LoanPaymentType::Late:
return detail::makeLatePayment(asset, view, loan, brokerSle, amount, j);
case LoanPaymentType::Regular:
case LoanPaymentType::Overpayment:
return detail::makeRegularPayment(asset, view, loan, brokerSle, amount, paymentType, j);
}
// -------------------------------------------------------------
// compute the periodic payment info that will be needed whether the
// payment is late or regular
detail::ExtendedPaymentComponents periodic{
detail::computePaymentComponents(
view.rules(),
asset,
loanScale,
totalValueOutstandingProxy,
principalOutstandingProxy,
managementFeeOutstandingProxy,
periodicPayment,
periodicRate,
paymentRemainingProxy,
managementFeeRate),
serviceFee};
XRPL_ASSERT_PARTS(
periodic.trackedPrincipalDelta >= 0,
"xrpl::loanMakePayment",
"regular payment valid principal");
// -------------------------------------------------------------
// late payment handling
if (paymentType == LoanPaymentType::Late)
{
TenthBips32 const lateInterestRate{loan->at(sfLateInterestRate)};
Number const latePaymentFee = loan->at(sfLatePaymentFee);
auto const latePaymentComponents = detail::computeLatePayment(
asset,
view,
principalOutstandingProxy,
nextDueDateProxy,
periodic,
lateInterestRate,
loanScale,
latePaymentFee,
amount,
managementFeeRate,
j);
if (latePaymentComponents.has_value())
{
return doPayment(
*latePaymentComponents,
totalValueOutstandingProxy,
principalOutstandingProxy,
managementFeeOutstandingProxy,
paymentRemainingProxy,
prevPaymentDateProxy,
nextDueDateProxy,
paymentInterval);
}
if (latePaymentComponents.error())
{
// error() will be the TER returned if a payment is not made. It
// will only evaluate to true if it's unsuccessful.
return std::unexpected(latePaymentComponents.error());
}
// LCOV_EXCL_START
UNREACHABLE("xrpl::loanMakePayment : invalid late payment result");
JLOG(j.error()) << "Late payment computation failed unexpectedly.";
return std::unexpected(tecINTERNAL);
// LCOV_EXCL_STOP
}
// -------------------------------------------------------------
// regular periodic payment handling
XRPL_ASSERT_PARTS(
paymentType == LoanPaymentType::Regular || paymentType == LoanPaymentType::Overpayment,
"xrpl::loanMakePayment",
"regular payment type");
// Keep a running total of the actual parts paid
LoanPaymentParts totalParts;
Number totalPaid;
std::size_t numPayments = 0;
while ((amount >= (totalPaid + periodic.totalDue)) && paymentRemainingProxy > 0 &&
numPayments < kLoanMaximumPaymentsPerTransaction)
{
// Try to make more payments
XRPL_ASSERT_PARTS(
periodic.trackedPrincipalDelta >= 0,
"xrpl::loanMakePayment",
"payment pays non-negative principal");
totalPaid += periodic.totalDue;
totalParts += detail::doPayment(
periodic,
totalValueOutstandingProxy,
principalOutstandingProxy,
managementFeeOutstandingProxy,
paymentRemainingProxy,
prevPaymentDateProxy,
nextDueDateProxy,
paymentInterval);
++numPayments;
XRPL_ASSERT_PARTS(
(periodic.specialCase == detail::PaymentSpecialCase::Final) ==
(paymentRemainingProxy == 0),
"xrpl::loanMakePayment",
"final payment is the final payment");
// Don't compute the next payment if this was the last payment
if (periodic.specialCase == detail::PaymentSpecialCase::Final)
break;
periodic = detail::ExtendedPaymentComponents{
detail::computePaymentComponents(
view.rules(),
asset,
loanScale,
totalValueOutstandingProxy,
principalOutstandingProxy,
managementFeeOutstandingProxy,
periodicPayment,
periodicRate,
paymentRemainingProxy,
managementFeeRate),
serviceFee};
}
if (numPayments == 0)
{
JLOG(j.warn()) << "Regular loan payment amount is insufficient. Due: " << periodic.totalDue
<< ", paid: " << amount;
return std::unexpected(tecINSUFFICIENT_PAYMENT);
}
XRPL_ASSERT_PARTS(
totalParts.principalPaid + totalParts.interestPaid + totalParts.feePaid == totalPaid,
"xrpl::loanMakePayment",
"payment parts add up");
XRPL_ASSERT_PARTS(totalParts.valueChange == 0, "xrpl::loanMakePayment", "no value change");
// -------------------------------------------------------------
// overpayment handling
//
// If the "fixCleanup3_1_3" amendment is enabled, truncate "amount",
// at the loan scale. If the raw value is used, the overpayment
// amount could be meaningless dust. Trying to process such a small
// amount will, at best, waste time when all the result values round
// to zero. At worst, it can cause logical errors with tiny amounts
// of interest that don't add up correctly.
auto const roundedAmount = view.rules().enabled(fixCleanup3_1_3)
? roundToAsset(asset, amount, loanScale, Number::RoundingMode::TowardsZero)
: amount;
if (paymentType == LoanPaymentType::Overpayment && loan->isFlag(lsfLoanOverpayment) &&
paymentRemainingProxy > 0 && totalPaid < roundedAmount &&
numPayments < kLoanMaximumPaymentsPerTransaction)
{
TenthBips32 const overpaymentInterestRate{loan->at(sfOverpaymentInterestRate)};
TenthBips32 const overpaymentFeeRate{loan->at(sfOverpaymentFee)};
// It shouldn't be possible for the overpayment to be greater than
// totalValueOutstanding, because that would have been processed as
// another normal payment. But cap it just in case.
Number const overpaymentRaw =
std::min(roundedAmount - totalPaid, *totalValueOutstandingProxy);
bool const fixEnabled = view.rules().enabled(fixCleanup3_2_0);
Number const overpayment = fixEnabled
? roundToAsset(asset, overpaymentRaw, loanScale, Number::RoundingMode::Downward)
: overpaymentRaw;
// Post-amendment, the rounded overpayment can be zero; pre-amendment
// it's always positive given the surrounding guards.
if (!fixEnabled || overpayment > 0)
{
detail::ExtendedPaymentComponents const overpaymentComponents =
detail::computeOverpaymentComponents(
view.rules(),
asset,
loanScale,
overpayment,
overpaymentInterestRate,
overpaymentFeeRate,
managementFeeRate);
// Don't process an overpayment if the whole amount (or more!)
// gets eaten by fees and interest.
if (overpaymentComponents.trackedPrincipalDelta > 0)
{
XRPL_ASSERT_PARTS(
overpaymentComponents.untrackedInterest >= beast::kZero,
"xrpl::loanMakePayment",
"overpayment penalty did not reduce value of loan");
// Can't just use `periodicPayment` here, because it might
// change
auto periodicPaymentProxy = loan->at(sfPeriodicPayment);
if (auto const overResult = detail::doOverpayment(
view.rules(),
asset,
loanScale,
overpaymentComponents,
totalValueOutstandingProxy,
principalOutstandingProxy,
managementFeeOutstandingProxy,
periodicPaymentProxy,
periodicRate,
paymentRemainingProxy,
managementFeeRate,
j))
{
totalParts += *overResult;
}
else if (overResult.error())
{
// error() will be the TER returned if a payment is not
// made. It will only evaluate to true if it's unsuccessful.
// Otherwise, tesSUCCESS means nothing was done, so
// continue.
return std::unexpected(overResult.error());
}
}
}
}
// Check the final results are rounded, to double-check that the
// intermediate steps were rounded.
XRPL_ASSERT(
isRounded(asset, totalParts.principalPaid, loanScale) &&
totalParts.principalPaid >= beast::kZero,
"xrpl::loanMakePayment : total principal paid is valid");
XRPL_ASSERT(
isRounded(asset, totalParts.interestPaid, loanScale) &&
totalParts.interestPaid >= beast::kZero,
"xrpl::loanMakePayment : total interest paid is valid");
XRPL_ASSERT(
isRounded(asset, totalParts.valueChange, loanScale),
"xrpl::loanMakePayment : loan value change is valid");
XRPL_ASSERT(
isRounded(asset, totalParts.feePaid, loanScale) && totalParts.feePaid >= beast::kZero,
"xrpl::loanMakePayment : fee paid is valid");
return totalParts;
// LCOV_EXCL_START
UNREACHABLE("xrpl::loanMakePayment : invalid payment type");
return std::unexpected(tecINTERNAL);
// LCOV_EXCL_STOP
}
} // namespace xrpl

View File

@@ -59,6 +59,10 @@ STValidation::validationFormat()
{sfBaseFeeDrops, SoeOptional},
{sfReserveBaseDrops, SoeOptional},
{sfReserveIncrementDrops, SoeOptional},
// featureSmartEscrow
{sfGasLimit, SoeOptional},
{sfBytecodeSizeLimit, SoeOptional},
{sfGasPrice, SoeOptional},
};
// clang-format on

View File

@@ -108,6 +108,8 @@ transResults()
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
MAKE_ERROR(tecBAD_PROOF, "Proof cannot be verified"),
MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Sponsor has not authorized this transaction."),
MAKE_ERROR(tecOUT_OF_GAS, "The WASM code ran out of gas during execution."),
MAKE_ERROR(tecBYTECODE_REJECTED, "The custom WASM code that was run rejected your transaction."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
@@ -133,6 +135,8 @@ transResults()
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
MAKE_ERROR(tefNO_DST_PARTIAL, "Partial payment to create account not allowed."),
MAKE_ERROR(tefBAD_PATH_COUNT, "Malformed: Too many paths."),
MAKE_ERROR(tefNO_BYTECODE, "There is no WASM code to run, but a WASM-specific field was included."),
MAKE_ERROR(tefBYTECODE_NOT_INCLUDED, "WASM code requires a field to be included that was not included."),
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
@@ -204,6 +208,9 @@ transResults()
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_CIPHERTEXT, "Malformed: Invalid ciphertext."),
MAKE_ERROR(temBAD_WASM, "Malformed: Provided WASM code is invalid."),
MAKE_ERROR(temINVALID_BYTECODE, "Malformed: Provided WASM code is invalid."),
MAKE_ERROR(temTEMP_DISABLED, "The transaction requires logic that is currently temporarily disabled."),
MAKE_ERROR(terRETRY, "Retry transaction."),
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),

View File

@@ -194,6 +194,12 @@ TxMeta::getAsObject() const
if (parentBatchID_.has_value())
metaData.setFieldH256(sfParentBatchID, *parentBatchID_);
if (gasUsed_.has_value())
metaData.setFieldU32(sfGasUsed, *gasUsed_);
if (vmReturnCode_.has_value())
metaData.setFieldI32(sfVMReturnCode, *vmReturnCode_);
return metaData;
}

View File

@@ -58,6 +58,13 @@ ApplyContext::discard()
std::optional<TxMeta>
ApplyContext::apply(TER ter)
{
if (vmReturnCode_.has_value())
{
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) view_ emplaced in constructor
view_->setVMReturnCode(*vmReturnCode_);
}
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) view_ emplaced in constructor
view_->setGasUsed(gasUsed_);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) view_ emplaced in constructor
return view_->apply(base_, tx, ter, parentBatchId_, (flags_ & TapDryRun) != 0u, journal);
}

View File

@@ -1283,6 +1283,23 @@ removeDeletedTrustLines(
}
}
static void
modifyWasmDataFields(
ApplyView& view,
std::vector<std::pair<uint256, SLE::const_pointer>> const& wasmObjects,
beast::Journal viewJ)
{
for (auto const& [index, after] : wasmObjects)
{
if (auto const sle = view.peek(keylet::escrow(index)))
{
auto const data = after->getFieldVL(sfData);
sle->setFieldVL(sfData, data);
view.update(sle);
}
}
}
/**
* Reset the context, discarding any changes made and adjust the fee.
*
@@ -1450,6 +1467,10 @@ Transactor::processPersistentChanges(TER result, XRPAmount fee)
types.insert(ltNFTOKEN_OFFER);
types.insert(ltCREDENTIAL);
}
else if (ter == tecBYTECODE_REJECTED)
{
types.insert(ltESCROW);
}
return types;
};
@@ -1459,10 +1480,11 @@ Transactor::processPersistentChanges(TER result, XRPAmount fee)
auto const typesToCollect = typesForResult(result);
std::map<LedgerEntryType, std::vector<uint256>> deletedObjects;
std::map<LedgerEntryType, std::vector<std::pair<uint256, SLE::const_pointer>>> modifiedObjects;
if (!typesToCollect.empty())
{
ctx_.visit(
[&typesToCollect, &deletedObjects](
[&typesToCollect, &deletedObjects, &modifiedObjects](
uint256 const& index, bool isDelete, SLE::const_ref before, SLE::const_ref after) {
if (isDelete)
{
@@ -1486,6 +1508,16 @@ Transactor::processPersistentChanges(TER result, XRPAmount fee)
}
}
}
else if (after)
{
// Collect modified escrows so that data written by a
// rejected WASM execution can be re-applied after the
// context is reset.
auto const type = after->getType();
if (typesToCollect.contains(type) && type == ltESCROW &&
after->isFieldPresent(sfData))
modifiedObjects[type].emplace_back(index, after);
}
});
}
@@ -1532,6 +1564,24 @@ Transactor::processPersistentChanges(TER result, XRPAmount fee)
// LCOV_EXCL_STOP
}
}
for (auto const& [type, ids] : modifiedObjects)
{
if (ids.empty() || !typesToApply.contains(type))
continue;
switch (type)
{
case ltESCROW:
modifyWasmDataFields(view(), ids, viewJ);
break;
// LCOV_EXCL_START
default:
UNREACHABLE(
"xrpl::Transactor::processPersistentChanges() : "
"unexpected type");
break;
// LCOV_EXCL_STOP
}
}
}
return {result, fee, isTecClaim(result)};
@@ -1651,7 +1701,8 @@ Transactor::operator()()
}
else if (
(result == tecOVERSIZE) || (result == tecKILLED) || (result == tecINCOMPLETE) ||
(result == tecEXPIRED) || (isTecClaimHardFail(result, view().flags())))
(result == tecEXPIRED) || (result == tecBYTECODE_REJECTED) ||
(isTecClaimHardFail(result, view().flags())))
{
std::tie(result, fee, applied) = processPersistentChanges(result, fee);
}

View File

@@ -209,7 +209,8 @@ EscrowCancel::doApply()
}
}
decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal);
auto const reserveToSubtract = calculateAdditionalReserve((*slep)[~sfBytecode]);
decreaseOwnerCountForObject(ctx_.view(), sle, slep, reserveToSubtract, ctx_.journal);
// Remove escrow from ledger
ctx_.view().erase(slep);

View File

@@ -6,9 +6,11 @@
#include <xrpl/conditions/Condition.h>
#include <xrpl/core/ServiceRegistry.h>
#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/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
@@ -32,6 +34,8 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/applySteps.h>
#include <xrpl/tx/wasm/HostFunc.h>
#include <xrpl/tx/wasm/WasmVM.h>
#include <memory>
#include <system_error>
@@ -88,8 +92,13 @@ EscrowCreate::checkExtraFeatures(PreflightContext const& ctx)
// Only require featureMPTokensV1 when the escrow amount is an MPT and
// fixCleanup3_2_0 is active; XRP/IOU escrows are unaffected by this gate.
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfAmount].holds<MPTIssue>())
return ctx.rules.enabled(featureMPTokensV1);
return true;
{
if (!ctx.rules.enabled(featureMPTokensV1))
return false;
}
return (!ctx.tx.isFieldPresent(sfBytecode) && !ctx.tx.isFieldPresent(sfData)) ||
ctx.rules.enabled(featureSmartEscrow);
}
template <ValidIssueType T>
@@ -124,6 +133,19 @@ escrowCreatePreflightHelper<MPTIssue>(PreflightContext const& ctx)
return tesSUCCESS;
}
XRPAmount
EscrowCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
{
XRPAmount txnFees{Transactor::calculateBaseFee(view, tx)};
if (tx.isFieldPresent(sfBytecode))
{
// 10 base fees for the transaction (1 is in
// `Transactor::calculateBaseFee`), plus 5 drops per byte
txnFees += 9 * view.fees().base + 5 * tx[sfBytecode].size();
}
return txnFees;
}
NotTEC
EscrowCreate::preflight(PreflightContext const& ctx)
{
@@ -155,12 +177,19 @@ EscrowCreate::preflight(PreflightContext const& ctx)
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
return temBAD_EXPIRATION;
if (ctx.tx.isFieldPresent(sfBytecode) && !ctx.tx.isFieldPresent(sfCancelAfter))
return temBAD_EXPIRATION;
// In the absence of a FinishAfter, the escrow can be finished
// immediately, which can be confusing. When creating an escrow,
// we want to ensure that either a FinishAfter time is explicitly
// specified or a completion condition is attached.
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition])
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition] && !ctx.tx[~sfBytecode])
{
JLOG(ctx.j.debug()) << "Must have at least one of FinishAfter, "
"Condition, or Bytecode.";
return temMALFORMED;
}
if (auto const cb = ctx.tx[~sfCondition])
{
@@ -176,6 +205,60 @@ EscrowCreate::preflight(PreflightContext const& ctx)
}
}
if (ctx.tx.isFieldPresent(sfData))
{
if (!ctx.tx.isFieldPresent(sfBytecode))
{
JLOG(ctx.j.debug()) << "EscrowCreate with Data requires Bytecode";
return temMALFORMED;
}
auto const data = ctx.tx.getFieldVL(sfData);
if (data.size() > kMaxWasmDataLength)
{
JLOG(ctx.j.debug()) << "EscrowCreate.Data bad size " << data.size();
return temMALFORMED;
}
}
if (ctx.tx.isFieldPresent(sfBytecode))
{
auto const fees(ctx.registry.get().getFees());
if (fees.bytecodeSizeLimit == 0 || fees.gasLimit == 0)
{
JLOG(ctx.j.debug()) << "WASM runtime deactivated by fee voting";
return temTEMP_DISABLED;
}
auto const code = ctx.tx.getFieldVL(sfBytecode);
if (code.empty() || code.size() > fees.bytecodeSizeLimit)
{
JLOG(ctx.j.debug()) << "EscrowCreate.Bytecode bad size " << code.size();
return temMALFORMED;
}
// actual validity of WASM code happens in `preflightSigValidated`
// (after the signature is checked)
}
return tesSUCCESS;
}
NotTEC
EscrowCreate::preflightSigValidated(PreflightContext const& ctx)
{
if (ctx.tx.isFieldPresent(sfBytecode))
{
auto const code = ctx.tx.getFieldVL(sfBytecode);
// basic checks happen in `preflight`
HostFunctions mock(ctx.j);
auto const re = preflightEscrowWasm(code, mock, escrowFunctionName);
if (!isTesSuccess(re))
{
JLOG(ctx.j.debug()) << "EscrowCreate.Bytecode bad WASM";
return re;
}
}
return tesSUCCESS;
}
@@ -434,6 +517,7 @@ EscrowCreate::doApply()
// Check reserve and funds availability
STAmount const amount{ctx_.tx[sfAmount]};
auto const reserveToAdd = calculateAdditionalReserve(ctx_.tx[~sfBytecode]);
auto const balance = sle->getFieldAmount(sfBalance).xrp();
// First check: whoever is on the hook for the new owner increment
@@ -441,8 +525,8 @@ EscrowCreate::doApply()
// validates the sponsor's reserve + remaining credit. When
// unsponsored this hits the source branch and validates the
// source's pre-lock balance against base + (currentOC+1)*increment.
if (auto const ret =
checkReserve(ctx_.getApplyViewContext(), sle, balance, {.ownerCountDelta = 1}, j_);
if (auto const ret = checkReserve(
ctx_.getApplyViewContext(), sle, balance, {.ownerCountDelta = reserveToAdd}, j_);
!isTesSuccess(ret))
return ret;
@@ -486,6 +570,8 @@ EscrowCreate::doApply()
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
(*slep)[~sfBytecode] = ctx_.tx[~sfBytecode];
(*slep)[~sfData] = ctx_.tx[~sfData];
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
{
@@ -553,7 +639,7 @@ EscrowCreate::doApply()
}
// increment owner count
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal);
increaseOwnerCount(ctx_.getApplyViewContext(), sle, reserveToAdd, ctx_.journal);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep);
ctx_.view().update(sle);
return tesSUCCESS;

View File

@@ -18,9 +18,11 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
@@ -29,9 +31,17 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/wasm/HostFuncImpl.h>
#include <xrpl/tx/wasm/WasmVM.h>
#include <cstdint>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <system_error>
#include <variant>
#include <vector>
namespace xrpl {
@@ -64,7 +74,13 @@ checkCondition(Slice f, Slice c)
bool
EscrowFinish::checkExtraFeatures(PreflightContext const& ctx)
{
return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials);
if (ctx.tx.isFieldPresent(sfCredentialIDs) && !ctx.rules.enabled(featureCredentials))
return false;
if (ctx.tx.isFieldPresent(sfGas) && !ctx.rules.enabled(featureSmartEscrow))
return false;
return true;
}
NotTEC
@@ -76,7 +92,32 @@ EscrowFinish::preflight(PreflightContext const& ctx)
// If you specify a condition, then you must also specify
// a fulfillment.
if (static_cast<bool>(cb) != static_cast<bool>(fb))
{
JLOG(ctx.j.debug()) << "Condition != Fulfillment";
return temMALFORMED;
}
if (auto const allowance = ctx.tx[~sfGas]; allowance)
{
auto const fees(ctx.registry.get().getFees());
if (fees.gasLimit == 0)
{
JLOG(ctx.j.debug()) << "WASM runtime deactivated by fee voting";
return temTEMP_DISABLED;
}
if (*allowance == 0)
{
return temBAD_LIMIT;
}
if (*allowance > fees.gasLimit)
{
JLOG(ctx.j.debug()) << "Gas too large: " << *allowance;
return temBAD_LIMIT;
}
}
if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
return err;
return tesSUCCESS;
}
@@ -110,9 +151,6 @@ EscrowFinish::preflightSigValidated(PreflightContext const& ctx)
}
}
if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
return err;
return tesSUCCESS;
}
@@ -125,7 +163,15 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
{
extraFee += view.fees().base * (32 + (fb->size() / 16));
}
if (std::optional<uint64_t> const allowance = tx[~sfGas]; allowance)
{
// The extra fee is the allowance in drops, rounded up to the nearest
// whole drop.
// Integer math rounds down by default, so we add 1 to round up.
uint64_t const allowanceFee =
(((*allowance) * view.fees().gasPrice) / microDropsPerDrop) + 1;
extraFee += allowanceFee;
}
return Transactor::calculateBaseFee(view, tx) + extraFee;
}
@@ -201,25 +247,50 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
return err;
}
if (ctx.view.rules().enabled(featureTokenEscrow))
if (ctx.view.rules().enabled(featureTokenEscrow) ||
ctx.view.rules().enabled(featureSmartEscrow))
{
// this check is done in doApply before this amendment is enabled
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
auto const slep = ctx.view.read(k);
if (!slep)
return tecNO_TARGET;
AccountID const dest = (*slep)[sfDestination];
STAmount const amount = (*slep)[sfAmount];
if (!isXRP(amount))
if (ctx.view.rules().enabled(featureSmartEscrow))
{
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
if (slep->isFieldPresent(sfBytecode))
{
if (!ctx.tx.isFieldPresent(sfGas))
{
JLOG(ctx.j.debug()) << "Bytecode requires Gas";
return tefBYTECODE_NOT_INCLUDED;
}
}
else
{
if (ctx.tx.isFieldPresent(sfGas))
{
JLOG(ctx.j.debug()) << "Bytecode not present, "
"Gas present";
return tefNO_BYTECODE;
}
}
}
if (ctx.view.rules().enabled(featureTokenEscrow))
{
AccountID const dest = (*slep)[sfDestination];
STAmount const amount = (*slep)[sfAmount];
if (!isXRP(amount))
{
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
}
}
}
return tesSUCCESS;
@@ -232,7 +303,8 @@ EscrowFinish::doApply()
auto const slep = ctx_.view().peek(k);
if (!slep)
{
if (ctx_.view().rules().enabled(featureTokenEscrow))
if (ctx_.view().rules().enabled(featureTokenEscrow) ||
ctx_.view().rules().enabled(featureSmartEscrow))
return tecINTERNAL; // LCOV_EXCL_LINE
return tecNO_TARGET;
@@ -250,6 +322,20 @@ EscrowFinish::doApply()
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
return tecNO_PERMISSION;
AccountID const destID = (*slep)[sfDestination];
auto const sled = ctx_.view().peek(keylet::account(destID));
if (ctx_.view().rules().enabled(featureSmartEscrow))
{
// NOTE: Escrow payments cannot be used to fund accounts.
if (!sled)
return tecNO_DST;
if (auto err =
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, destID, sled, ctx_.journal);
!isTesSuccess(err))
return err;
}
// Check cryptocondition fulfillment
{
auto const id = ctx_.tx.getTransactionID();
@@ -303,16 +389,71 @@ EscrowFinish::doApply()
return tecCRYPTOCONDITION_ERROR;
}
// NOTE: Escrow payments cannot be used to fund accounts.
AccountID const destID = (*slep)[sfDestination];
auto const sled = ctx_.view().peek(keylet::account(destID));
if (!sled)
return tecNO_DST;
if (!ctx_.view().rules().enabled(featureSmartEscrow))
{
// NOTE: Escrow payments cannot be used to fund accounts.
if (!sled)
return tecNO_DST;
if (auto err =
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, destID, sled, ctx_.journal);
!isTesSuccess(err))
return err;
if (auto err =
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, destID, sled, ctx_.journal);
!isTesSuccess(err))
return err;
}
// Execute custom release function
if ((*slep)[~sfBytecode])
{
JLOG(j_.trace()) << "The escrow has a finish function, running WASM code...";
// WASM execution
auto const wasmStr = slep->getFieldVL(sfBytecode);
std::vector<uint8_t> const wasm(wasmStr.begin(), wasmStr.end());
WasmHostFunctionsImpl ledgerDataProvider(ctx_, k);
if (!ctx_.tx.isFieldPresent(sfGas))
{
// already checked above, this check is just in case
return tecINTERNAL;
}
std::uint32_t const allowance = ctx_.tx[sfGas];
auto re = runEscrowWasm(wasm, ledgerDataProvider, allowance, escrowFunctionName);
JLOG(j_.trace()) << "Escrow WASM ran";
if (auto const& data = ledgerDataProvider.getData(); data.has_value())
{
if (data->size() > kMaxWasmDataLength)
{
// should already be checked in the updateData host function
return tecINTERNAL; // LCOV_EXCL_LINE
}
slep->setFieldVL(sfData, makeSlice(*data));
ctx_.view().update(slep);
}
if (re.has_value())
{
auto const reValue = re.value().result;
auto const reCost = re.value().cost;
JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue) << ", cost: " << reCost;
ctx_.setVMReturnCode(reValue);
if (reCost < 0 || reCost > std::numeric_limits<uint32_t>::max())
return tecINTERNAL; // LCOV_EXCL_LINE
ctx_.setGasUsed(static_cast<uint32_t>(reCost));
if (reValue <= 0)
{
return tecBYTECODE_REJECTED;
}
}
else
{
JLOG(j_.debug()) << "WASM Failure: " + transHuman(re.error().ter);
return re.error().ter;
}
}
AccountID const account = (*slep)[sfAccount];
@@ -340,6 +481,8 @@ EscrowFinish::doApply()
}
}
auto const reserveToSubtract = calculateAdditionalReserve((*slep)[~sfBytecode]);
// With the Sponsor amendment, release the escrow reserve before delivery.
// Token delivery can auto-create a destination holding, and the same
// sponsor (or the same account, for a self-escrow) may cover both the
@@ -348,7 +491,7 @@ EscrowFinish::doApply()
// arithmetic for self-escrows and would break consensus if not gated.
bool const sponsorEnabled = ctx_.view().rules().enabled(featureSponsor);
if (sponsorEnabled)
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
decreaseOwnerCountForObject(ctx_.view(), account, slep, reserveToSubtract, ctx_.journal);
STAmount const amount = slep->getFieldAmount(sfAmount);
// Transfer amount to destination
@@ -401,7 +544,7 @@ EscrowFinish::doApply()
// Adjust source owner count (legacy position, pre-Sponsor)
if (!sponsorEnabled)
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
decreaseOwnerCountForObject(ctx_.view(), account, slep, reserveToSubtract, ctx_.journal);
// Remove escrow from ledger
ctx_.view().erase(slep);

View File

@@ -9,6 +9,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/AmendmentTable.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/PublicKey.h>
@@ -123,6 +124,22 @@ Change::preclaim(PreclaimContext const& ctx)
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
return temDISABLED;
}
if (ctx.view.rules().enabled(featureSmartEscrow))
{
if (!ctx.tx.isFieldPresent(sfGasLimit) ||
!ctx.tx.isFieldPresent(sfBytecodeSizeLimit) ||
!ctx.tx.isFieldPresent(sfGasPrice))
return temMALFORMED;
if (ctx.tx[sfGasLimit] > kMaxGasLimit ||
ctx.tx[sfBytecodeSizeLimit] > kMaxBytecodeSizeLimit)
return temBAD_FEE;
}
else
{
if (ctx.tx.isFieldPresent(sfGasLimit) ||
ctx.tx.isFieldPresent(sfBytecodeSizeLimit) || ctx.tx.isFieldPresent(sfGasPrice))
return temDISABLED;
}
return tesSUCCESS;
case ttAMENDMENT:
case ttUNL_MODIFY:
@@ -284,6 +301,12 @@ Change::applyFee()
set(feeObject, ctx_.tx, sfReserveBase);
set(feeObject, ctx_.tx, sfReserveIncrement);
}
if (view().rules().enabled(featureSmartEscrow))
{
set(feeObject, ctx_.tx, sfGasLimit);
set(feeObject, ctx_.tx, sfBytecodeSizeLimit);
set(feeObject, ctx_.tx, sfGasPrice);
}
view().update(feeObject);

View File

@@ -0,0 +1,52 @@
#include <xrpl/tx/wasm/HostFuncImpl.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/digest.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <cstdint>
#include <expected>
namespace xrpl {
// =========================================================
// SECTION: WRITE FUNCTION
// =========================================================
std::expected<int32_t, HostFunctionError>
WasmHostFunctionsImpl::updateData(Slice const& data)
{
if (data.size() > kMaxWasmDataLength)
return std::unexpected(HostFunctionError::DataFieldTooLarge);
data_ = Bytes(data.begin(), data.end());
return data_->size();
}
// =========================================================
// SECTION: UTILS
// =========================================================
std::expected<int32_t, HostFunctionError>
WasmHostFunctionsImpl::checkSignature(
Slice const& message,
Slice const& signature,
Slice const& pubkey) const
{
if (!publicKeyType(pubkey))
return std::unexpected(HostFunctionError::InvalidParams);
PublicKey const pk(pubkey);
return verify(pk, message, signature);
}
std::expected<Hash, HostFunctionError>
WasmHostFunctionsImpl::computeSha512HalfHash(Slice const& data) const
{
auto const hash = sha512Half(data);
return hash;
}
} // namespace xrpl

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