Compare commits

...

164 Commits

Author SHA1 Message Date
Denis Angell
42cbecb9e4 feature
Co-authored-by: Mayukha Vadari <mvadari@gmail.com>
2026-04-06 10:42:02 -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
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
f8a149c675 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2026-03-24 09:10:37 -07:00
Olek
780380da7e Modularization fixes (#6488) 2026-03-10 11:37:12 -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
50a74b899d add defensive check for sfData length (#6449) 2026-03-02 14:47:47 -05:00
Mayukha Vadari
446ad36cbb Merge branch 'ripple/se/fees' into ripple/smart-escrow 2026-02-10 17:47:32 -05:00
Mayukha Vadari
fd14054f17 update preflight checks (#6094) 2026-02-09 17:36:02 -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
f01ac563a9 Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow 2026-02-04 18:07:12 -05:00
Mayukha Vadari
a1844086d7 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2026-02-03 15:32:21 -05:00
Mayukha Vadari
ac173b6827 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2026-01-28 17:14:13 -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
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
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
9538e9b34c Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow 2026-01-15 14:34:54 -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
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
ff39fa59d9 Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow 2026-01-12 14:12:04 -05:00
Mayukha Vadari
8015088340 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2026-01-08 11:46:18 -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
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
e916416642 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-12-15 10:47:26 -08:00
Mayukha Vadari
6a54ed7f14 fix fee overflow issue in EscrowFinish (#6130) 2025-12-12 15:12:43 -08:00
Olek
1e0741690d Fix sign cost (#6103) 2025-12-03 18:27:06 -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
abfcc4ef67 fix tests 2025-11-25 04:17:42 +05:30
Mayukha Vadari
dba187f8c5 fix build issues 2025-11-25 03:42:38 +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
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
fa8aa49376 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-11-04 14:51:11 -05:00
Mayukha Vadari
a891b49c67 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-11-04 14:24:51 -05:00
Mayukha Vadari
7e2e10f02c Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-24 18:01:33 -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
b6bd268be2 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-24 16:02:23 -04:00
Mayukha Vadari
566b85b3d6 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-23 15:41:53 -04:00
Mayukha Vadari
7d22fe804d Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-23 15:38:46 -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
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
Mayukha Vadari
f16f243c22 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-13 14:01:11 -04:00
Mayukha Vadari
e41f6a71b7 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-09 17:13:13 -04:00
Mayukha Vadari
db263b696c Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-10-06 17:01:32 -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
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
8729688feb Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow 2025-09-26 16:56:51 -04:00
Mayukha Vadari
f1f798bb85 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-09-26 15:52:22 -04:00
Mayukha Vadari
85bff20ae5 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-09-23 10:22:30 -04:00
Mayukha Vadari
e6592e93a9 Merge branch 'ripple/se/fees' into ripple/smart-escrow 2025-09-22 18:32:36 -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
5c480cf883 Merge branch 'develop' into ripple/smart-escrow 2025-09-10 14:51:45 -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
180 changed files with 24562 additions and 564 deletions

2
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,2 @@
# Allow anyone to review any change by default.
*

View File

@@ -99,6 +99,7 @@ words:
- distro
- doxyfile
- dxrpl
- emittance
- endmacro
- exceptioned
- Falco
@@ -248,6 +249,7 @@ words:
- statsd
- STATSDCOLLECTOR
- stissue
- stjson
- stnum
- stobj
- stobject

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/SHAMapHash.h>
#include <xrpl/basics/TaggedCache.h>
#include <xrpl/protocol/Fees.h>
#include <boost/asio.hpp>
@@ -238,6 +239,9 @@ public:
virtual DatabaseCon&
getWalletDB() = 0;
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

@@ -54,6 +54,18 @@ public:
deliver_ = amount;
}
void
setGasUsed(std::optional<std::uint32_t> const gasUsed)
{
gasUsed_ = gasUsed;
}
void
setWasmReturnCode(std::int32_t const wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
/** Get the number of modified entries
*/
std::size_t
@@ -72,6 +84,8 @@ public:
private:
std::optional<STAmount> deliver_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> wasmReturnCode_;
};
} // namespace xrpl

View File

@@ -0,0 +1,82 @@
#pragma once
#include <xrpl/ledger/OpenView.h>
#include <memory>
namespace xrpl {
class OpenViewSandbox
{
private:
OpenView& parent_;
std::unique_ptr<OpenView> sandbox_;
public:
using key_type = ReadView::key_type;
OpenViewSandbox(OpenView& parent)
: parent_(parent), sandbox_(std::make_unique<OpenView>(batch_view, parent))
{
}
void
rawErase(std::shared_ptr<SLE> const& sle)
{
sandbox_->rawErase(sle);
}
void
rawInsert(std::shared_ptr<SLE> const& sle)
{
sandbox_->rawInsert(sle);
}
void
rawReplace(std::shared_ptr<SLE> const& sle)
{
sandbox_->rawReplace(sle);
}
void
rawDestroyXRP(XRPAmount const& fee)
{
sandbox_->rawDestroyXRP(fee);
}
void
rawTxInsert(
key_type const& key,
std::shared_ptr<Serializer const> const& txn,
std::shared_ptr<Serializer const> const& metaData)
{
sandbox_->rawTxInsert(key, txn, metaData);
}
void
commit()
{
sandbox_->apply(parent_);
sandbox_ = std::make_unique<OpenView>(batch_view, parent_);
}
void
discard()
{
sandbox_ = std::make_unique<OpenView>(batch_view, parent_);
}
OpenView const&
view() const
{
return *sandbox_;
}
OpenView&
view()
{
return *sandbox_;
}
};
} // namespace xrpl

View File

@@ -3,6 +3,10 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -211,6 +215,27 @@ doWithdraw(
STAmount const& amount,
beast::Journal j);
enum class SendIssuerHandling { ihSENDER_NOT_ALLOWED, ihRECEIVER_NOT_ALLOWED, ihIGNORE };
enum class SendEscrowHandling { ehIGNORE, ehCHECK };
enum class SendAuthHandling { ahCHECK_SENDER, ahCHECK_RECEIVER, ahBOTH, ahNEITHER };
enum class SendFreezeHandling { fhCHECK_SENDER, fhCHECK_RECEIVER, fhBOTH, fhNEITHER };
enum class SendTransferHandling { thIGNORE, thCHECK };
enum class SendBalanceHandling { bhIGNORE, bhCHECK };
TER
canTransferFT(
ReadView const& view,
AccountID const& sender,
AccountID const& receiver,
STAmount const& amount,
beast::Journal j,
SendIssuerHandling issuerHandling,
SendEscrowHandling escrowHandling,
SendAuthHandling authHandling,
SendFreezeHandling freezeHandling,
SendTransferHandling transferHandling,
SendBalanceHandling balanceHandling);
/** Deleter function prototype. Returns the status of the entry deletion
* (if should not be skipped) and if the entry should be skipped. The status
* is always tesSUCCESS if the entry should be skipped.

View File

@@ -52,6 +52,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& wasmReturnCode,
bool isDryRun,
beast::Journal j);

View File

@@ -0,0 +1,102 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STData.h>
#include <xrpl/protocol/STJson.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <map>
namespace xrpl {
class ContractDataMap : public std::map<xrpl::AccountID, std::pair<bool, STJson>>
{
public:
uint32_t modifiedCount = 0;
};
class ContractEventMap : public std::map<std::string, STJson>
{
};
namespace contract {
/** The maximum number of data modifications in a single function. */
int64_t constexpr maxDataModifications = 1000;
/** The maximum number of bytes the data can occupy. */
int64_t constexpr maxContractDataSize = 1024;
/** The multiplier for contract data size calculations. */
int64_t constexpr dataByteMultiplier = 512;
/** The cost multiplier of creating a contract in bytes. */
int64_t constexpr createByteMultiplier = 500ULL;
/** The value to return when the fee calculation failed. */
int64_t constexpr feeCalculationFailed = 0x7FFFFFFFFFFFFFFFLL;
/** The maximum number of contract parameters that can be in a transaction. */
std::size_t constexpr maxContractParams = 8;
/** The maximum number of contract functions that can be in a transaction. */
std::size_t constexpr maxContractFunctions = 8;
int64_t
contractCreateFee(uint64_t byteCount);
NotTEC
preflightFunctions(STTx const& tx, beast::Journal j);
NotTEC
preflightInstanceParameters(STTx const& tx, beast::Journal j);
bool
validateParameterMapping(STArray const& params, STArray const& values, beast::Journal j);
NotTEC
preflightInstanceParameterValues(STTx const& tx, beast::Journal j);
NotTEC
preflightFlagParameters(STArray const& parameters, beast::Journal j);
bool
isValidParameterFlag(std::uint32_t flags);
TER
preclaimFlagParameters(
ReadView const& view,
AccountID const& sourceAccount,
AccountID const& contractAccount,
STArray const& parameters,
beast::Journal j);
TER
doApplyFlagParameters(
ApplyView& view,
STTx const& tx,
AccountID const& sourceAccount,
AccountID const& contractAccount,
STArray const& parameters,
XRPAmount const& priorBalance,
beast::Journal j);
TER
finalizeContractData(
ServiceRegistry& registry,
ApplyView& view,
AccountID const& contractAccount,
ContractDataMap const& dataMap,
ContractEventMap const& eventMap,
uint256 const& txnID);
} // namespace contract
} // namespace xrpl

View File

@@ -1,12 +1,12 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/nft.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
@@ -140,6 +140,13 @@ checkTrustlineDeepFrozen(
beast::Journal const j,
Issue const& issue);
TER
transferNFToken(
ApplyView& view,
AccountID const& buyer,
AccountID const& seller,
uint256 const& nftokenID);
} // namespace nft
} // namespace xrpl

View File

@@ -0,0 +1,78 @@
#pragma once
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFormats.h>
#include <optional>
#include <string>
#include <unordered_map>
namespace xrpl {
/**
* We have both transaction type emitables and granular type emitables.
* Since we will reuse the TransactionFormats to parse the Transaction
* Emitables, only the GranularEmitableType is defined here. To prevent
* conflicts with TxType, the GranularEmitableType is always set to a value
* greater than the maximum value of uint16.
*/
enum GranularEmitableType : std::uint32_t {
#pragma push_macro("EMITABLE")
#undef EMITABLE
#define EMITABLE(type, txType, value) type = value,
#include <xrpl/protocol/detail/emitable.macro>
#undef EMITABLE
#pragma pop_macro("EMITABLE")
};
enum Emittance { emitable, notEmitable };
class Emitable
{
private:
Emitable();
std::unordered_map<std::uint16_t, Emittance> emitableTx_;
std::unordered_map<std::string, GranularEmitableType> granularEmitableMap_;
std::unordered_map<GranularEmitableType, std::string> granularNameMap_;
std::unordered_map<GranularEmitableType, TxType> granularTxTypeMap_;
public:
static Emitable const&
getInstance();
Emitable(Emitable const&) = delete;
Emitable&
operator=(Emitable const&) = delete;
std::optional<std::string>
getEmitableName(std::uint32_t const value) const;
std::optional<std::uint32_t>
getGranularValue(std::string const& name) const;
std::optional<std::string>
getGranularName(GranularEmitableType const& value) const;
std::optional<TxType>
getGranularTxType(GranularEmitableType const& gpType) const;
bool
isEmitable(std::uint32_t const& emitableValue) const;
// for tx level emitable, emitable value is equal to tx type plus one
uint32_t
txToEmitableType(TxType const& type) const;
// tx type value is emitable value minus one
TxType
emitableToTxType(uint32_t const& value) const;
};
} // namespace xrpl

View File

@@ -207,6 +207,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 */
Keylet
payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept;
@@ -342,6 +348,22 @@ permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept;
Keylet
permissionedDomain(uint256 const& domainID) noexcept;
Keylet
contractSource(uint256 const& contractHash) noexcept;
Keylet
contract(uint256 const& contractHash, AccountID const& owner, std::uint32_t seq) noexcept;
inline Keylet
contract(uint256 const& contractID)
{
return {ltCONTRACT, contractID};
}
Keylet
contractData(AccountID const& owner, AccountID const& contractAccount) noexcept;
} // namespace keylet
// Everything below is deprecated and should be removed in favor of keylets:

View File

@@ -74,14 +74,6 @@ enum LedgerEntryType : std::uint16_t {
*/
ltNICKNAME [[deprecated("This object type is not supported and should not be used.")]] = 0x006e,
/** A legacy, deprecated type.
\deprecated **This object type is not supported and should not be used.**
Support for this type of object was never implemented.
No objects of this type were ever created.
*/
ltCONTRACT [[deprecated("This object type is not supported and should not be used.")]] = 0x0063,
/** A legacy, deprecated type.
\deprecated **This object type is not supported and should not be used.**

View File

@@ -33,6 +33,9 @@ class STNumber;
class STXChainBridge;
class STVector256;
class STCurrency;
class STData;
class STDataType;
class STJson;
#pragma push_macro("XMACRO")
#undef XMACRO
@@ -71,6 +74,9 @@ class STCurrency;
STYPE(STI_ISSUE, 24) \
STYPE(STI_XCHAIN_BRIDGE, 25) \
STYPE(STI_CURRENCY, 26) \
STYPE(STI_DATA, 27) \
STYPE(STI_DATATYPE, 28) \
STYPE(STI_JSON, 29) \
\
/* high-level types */ \
/* cannot be serialized inside other types */ \
@@ -350,6 +356,9 @@ using SF_NUMBER = TypedField<STNumber>;
using SF_VL = TypedField<STBlob>;
using SF_VECTOR256 = TypedField<STVector256>;
using SF_XCHAIN_BRIDGE = TypedField<STXChainBridge>;
using SF_DATA = TypedField<STData>;
using SF_DATATYPE = TypedField<STDataType>;
using SF_JSON = TypedField<STJson>;
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,289 @@
#pragma once
#include <xrpl/basics/Buffer.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAccount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/STBitString.h>
#include <xrpl/protocol/STInteger.h>
#include <xrpl/protocol/detail/STVar.h>
#include <cstdint>
#include <string>
#include <vector>
namespace xrpl {
class STData final : public STBase
{
private:
using data_type = detail::STVar;
std::uint16_t inner_type_;
data_type data_;
bool default_{true};
public:
using value_type = STData; // Although not directly holding a single value
STData(SField const& n);
STData(SField const& n, unsigned char);
STData(SField const& n, std::uint16_t);
STData(SField const& n, std::uint32_t);
STData(SField const& n, std::uint64_t);
STData(SField const& n, uint128 const&);
STData(SField const& n, uint160 const&);
STData(SField const& n, uint192 const&);
STData(SField const& n, uint256 const&);
STData(SField const& n, Blob const&);
STData(SField const& n, Slice const&);
STData(SField const& n, AccountID const&);
STData(SField const& n, STAmount const&);
STData(SField const& n, STIssue const&);
STData(SField const& n, STCurrency const&);
STData(SField const& n, STNumber const&);
STData(SerialIter& sit, SField const& name);
std::size_t
size() const;
SerializedTypeID
getSType() const override;
std::string
getInnerTypeString() const;
std::string
getText() const override;
Json::Value getJson(JsonOptions) const override;
void
add(Serializer& s) const override;
bool
isEquivalent(STBase const& t) const override;
bool
isDefault() const override;
SerializedTypeID
getInnerSType() const noexcept;
STBase*
makeFieldPresent();
void
setFieldU8(unsigned char);
void
setFieldU16(std::uint16_t);
void
setFieldU32(std::uint32_t);
void
setFieldU64(std::uint64_t);
void
setFieldH128(uint128 const&);
void
setFieldH160(uint160 const&);
void
setFieldH192(uint192 const&);
void
setFieldH256(uint256 const&);
void
setFieldVL(Blob const&);
void
setFieldVL(Slice const&);
void
setAccountID(AccountID const&);
void
setFieldAmount(STAmount const&);
void
setIssue(STIssue const&);
void
setCurrency(STCurrency const&);
void
setFieldNumber(STNumber const&);
unsigned char
getFieldU8() const;
std::uint16_t
getFieldU16() const;
std::uint32_t
getFieldU32() const;
std::uint64_t
getFieldU64() const;
uint128
getFieldH128() const;
uint160
getFieldH160() const;
uint192
getFieldH192() const;
uint256
getFieldH256() const;
Blob
getFieldVL() const;
AccountID
getAccountID() const;
STAmount const&
getFieldAmount() const;
STIssue
getFieldIssue() const;
STCurrency
getFieldCurrency() const;
STNumber
getFieldNumber() const;
private:
STBase*
copy(std::size_t n, void* buf) const override;
STBase*
move(std::size_t n, void* buf) override;
friend class detail::STVar;
// Implementation for getting (most) fields that return by value.
//
// The remove_cv and remove_reference are necessitated by the STBitString
// types. Their value() returns by const ref. We return those types
// by value.
template <
typename T,
typename V = typename std::remove_cv<
typename std::remove_reference<decltype(std::declval<T>().value())>::type>::type>
V
getFieldByValue() const;
// Implementations for getting (most) fields that return by const reference.
//
// If an absent optional field is deserialized we don't have anything
// obvious to return. So we insist on having the call provide an
// 'empty' value we return in that circumstance.
template <typename T, typename V>
V const&
getFieldByConstRef(V const& empty) const;
// Implementation for setting most fields with a setValue() method.
template <typename T, typename V>
void
setFieldUsingSetValue(V value);
// Implementation for setting fields using assignment
template <typename T>
void
setFieldUsingAssignment(T const& value);
};
//------------------------------------------------------------------------------
// Implementation
//------------------------------------------------------------------------------
inline SerializedTypeID
STData::getInnerSType() const noexcept
{
return static_cast<SerializedTypeID>(inner_type_);
}
template <typename T, typename V>
V
STData::getFieldByValue() const
{
STBase const* rf = &data_.get();
// if (!rf)
// throwFieldNotFound(getFName());
SerializedTypeID id = rf->getSType();
if (id == STI_NOTPRESENT)
Throw<std::runtime_error>("Field not present");
T const* cf = dynamic_cast<T const*>(rf);
if (!cf)
Throw<std::runtime_error>("Wrong field type");
return cf->value();
}
// Implementations for getting (most) fields that return by const reference.
//
// If an absent optional field is deserialized we don't have anything
// obvious to return. So we insist on having the call provide an
// 'empty' value we return in that circumstance.
template <typename T, typename V>
V const&
STData::getFieldByConstRef(V const& empty) const
{
STBase const* rf = &data_.get();
// if (!rf)
// throwFieldNotFound(field);
SerializedTypeID id = rf->getSType();
if (id == STI_NOTPRESENT)
return empty; // optional field not present
T const* cf = dynamic_cast<T const*>(rf);
if (!cf)
Throw<std::runtime_error>("Wrong field type");
return *cf;
}
// Implementation for setting most fields with a setValue() method.
template <typename T, typename V>
void
STData::setFieldUsingSetValue(V value)
{
static_assert(!std::is_lvalue_reference<V>::value, "");
STBase* rf = &data_.get();
// if (!rf)
// throwFieldNotFound(field);
if (rf->getSType() == STI_NOTPRESENT)
rf = makeFieldPresent();
T* cf = dynamic_cast<T*>(rf);
if (!cf)
Throw<std::runtime_error>("Wrong field type");
cf->setValue(std::move(value));
}
// Implementation for setting fields using assignment
template <typename T>
void
STData::setFieldUsingAssignment(T const& value)
{
STBase* rf = &data_.get();
// if (!rf)
// throwFieldNotFound(field);
// if (rf->getSType() == STI_NOTPRESENT)
// rf = makeFieldPresent(field);
T* cf = dynamic_cast<T*>(rf);
if (!cf)
Throw<std::runtime_error>("Wrong field type");
(*cf) = value;
}
//------------------------------------------------------------------------------
//
// Creation
//
//------------------------------------------------------------------------------
STData
dataFromJson(SField const& field, Json::Value const& value);
} // namespace xrpl

View File

@@ -0,0 +1,87 @@
#pragma once
#include <xrpl/basics/Buffer.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAccount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/STBitString.h>
#include <xrpl/protocol/STInteger.h>
#include <xrpl/protocol/detail/STVar.h>
#include <cstdint>
#include <string>
#include <vector>
namespace xrpl {
class STDataType final : public STBase
{
private:
std::uint16_t inner_type_;
bool default_{true};
public:
using value_type = STDataType; // Although not directly holding a single value
STDataType(SField const& n);
STDataType(SField const& n, SerializedTypeID);
STDataType(SerialIter& sit, SField const& name);
SerializedTypeID
getSType() const override;
std::string
getInnerTypeString() const;
std::string
getText() const override;
Json::Value getJson(JsonOptions) const override;
void
add(Serializer& s) const override;
bool
isEquivalent(STBase const& t) const override;
bool
isDefault() const override;
void setInnerSType(SerializedTypeID);
SerializedTypeID
getInnerSType() const noexcept;
STBase*
makeFieldPresent();
STBase*
copy(std::size_t n, void* buf) const override;
STBase*
move(std::size_t n, void* buf) override;
friend class detail::STVar;
};
//------------------------------------------------------------------------------
// Implementation
//------------------------------------------------------------------------------
inline SerializedTypeID
STDataType::getInnerSType() const noexcept
{
return static_cast<SerializedTypeID>(inner_type_);
}
//------------------------------------------------------------------------------
//
// Creation
//
//------------------------------------------------------------------------------
STDataType
dataTypeFromJson(SField const& field, Json::Value const& value);
} // namespace xrpl

View File

@@ -0,0 +1,193 @@
#pragma once
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/Serializer.h>
#include <map>
#include <memory>
#include <string>
#include <variant>
#include <vector>
namespace xrpl {
/**
* STJson: Serialized Type for JSON-like structures (objects or arrays).
*
* Supports two modes:
* - Object: Key-value pairs where keys are VL-encoded strings
* - Array: Ordered list of values
*
* Values are [SType marker][VL-encoded SType serialization].
* Values can be any SType, including nested STJson.
*
* Serialization format: [type_byte][VL_length][data...]
* - type_byte: 0x00 = Object, 0x01 = Array
*/
class STJson : public STBase
{
public:
enum class JsonType : uint8_t { Object = 0x00, Array = 0x01 };
using value_type = STJson;
value_type
value() const
{
return *this;
}
using Key = std::string;
using Value = std::shared_ptr<STBase>;
using Map = std::map<Key, Value>;
using Array = std::vector<Value>;
STJson() = default;
explicit STJson(Map&& map);
explicit STJson(Array&& array);
explicit STJson(SField const& name);
explicit STJson(SerialIter& sit, SField const& name);
SerializedTypeID
getSType() const override;
// Type checking
bool
isArray() const;
bool
isObject() const;
JsonType
getType() const;
// Depth checking (0 = no nesting, 1 = one level of nesting)
int
getDepth() const;
// Parse from binary blob
static std::shared_ptr<STJson>
fromBlob(void const* data, std::size_t size);
// Parse from SerialIter
static std::shared_ptr<STJson>
fromSerialIter(SerialIter& sit);
// Serialize to binary
void
add(Serializer& s) const override;
// JSON representation
Json::Value
getJson(JsonOptions options) const override;
bool
isEquivalent(STBase const& t) const override;
bool
isDefault() const override;
// Blob representation
Blob
toBlob() const;
// STJson size
std::size_t
size() const;
// Object accessors (only valid when isObject() == true)
Map const&
getMap() const;
void
setObjectField(Key const& key, Value const& value);
std::optional<STJson::Value>
getObjectField(Key const& key) const;
void
setNestedObjectField(Key const& key, Key const& nestedKey, Value const& value);
std::optional<Value>
getNestedObjectField(Key const& key, Key const& nestedKey) const;
// Array accessors (only valid when isArray() == true)
Array const&
getArray() const;
void
pushArrayElement(Value const& value);
std::optional<Value>
getArrayElement(size_t index) const;
void
setArrayElement(size_t index, Value const& value);
void
setArrayElementField(size_t index, Key const& key, Value const& value);
std::optional<Value>
getArrayElementField(size_t index, Key const& key) const;
size_t
arraySize() const;
// Nested array accessors (for arrays stored in object fields)
void
setNestedArrayElement(Key const& key, size_t index, Value const& value);
void
setNestedArrayElementField(
Key const& key,
size_t index,
Key const& nestedKey,
Value const& value);
std::optional<Value>
getNestedArrayElement(Key const& key, size_t index) const;
std::optional<Value>
getNestedArrayElementField(Key const& key, size_t index, Key const& nestedKey) const;
// Factory for SType value from blob (with SType marker)
static Value
makeValueFromVLWithType(SerialIter& sit);
void
setValue(STJson const& v);
private:
std::variant<Map, Array> data_{Map{}};
bool default_{false};
// Helper: validate nesting depth (max 1 level)
void
validateDepth(Value const& value, int currentDepth) const;
// Helper: parse a single key-value pair from SerialIter
static std::pair<Key, Value>
parsePair(SerialIter& sit);
// Helper: parse array elements from SerialIter
static Array
parseArray(SerialIter& sit, int length);
// Helper: encode a key as VL
static void
addVLKey(Serializer& s, std::string const& str);
// Helper: encode a value as [SType marker][VL]
static void
addVLValue(Serializer& s, std::shared_ptr<STBase> const& value);
STBase*
copy(std::size_t n, void* buf) const override;
STBase*
move(std::size_t n, void* buf) override;
friend class detail::STVar;
};
} // namespace xrpl

View File

@@ -11,6 +11,7 @@
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/STCurrency.h>
#include <xrpl/protocol/STIssue.h>
#include <xrpl/protocol/STJson.h>
#include <xrpl/protocol/STPathSet.h>
#include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/Units.h>
@@ -216,6 +217,10 @@ public:
getFieldI32(SField const& field) const;
AccountID
getAccountID(SField const& field) const;
STData
getFieldData(SField const& field) const;
STDataType
getFieldDataType(SField const& field) const;
Blob
getFieldVL(SField const& field) const;
@@ -234,6 +239,8 @@ public:
getFieldCurrency(SField const& field) const;
STNumber const&
getFieldNumber(SField const& field) const;
STJson const&
getFieldJson(SField const& field) const;
/** Get the value of a field.
@param A TypedField built from an SField value representing the desired
@@ -338,6 +345,9 @@ public:
void
set(STBase&& v);
void
addFieldFromSlice(SField const& sfield, Slice const& data);
void
setFieldU8(SField const& field, unsigned char);
void
@@ -376,6 +386,8 @@ public:
setFieldArray(SField const& field, STArray const& v);
void
setFieldObject(SField const& field, STObject const& v);
void
setFieldJson(SField const& field, STJson const& v);
template <class Tag>
void

View File

@@ -123,6 +123,7 @@ enum TEMcodes : TERUnderlyingType {
temINVALID_INNER_BATCH,
temBAD_WASM,
temTEMP_DISABLED,
};
//------------------------------------------------------------------------------
@@ -167,6 +168,8 @@ enum TEFcodes : TERUnderlyingType {
tefNO_TICKET,
tefNFTOKEN_IS_NOT_TRANSFERABLE,
tefINVALID_LEDGER_FIX_TYPE,
tefNO_WASM,
tefWASM_FIELD_NOT_INCLUDED,
};
//------------------------------------------------------------------------------
@@ -348,6 +351,8 @@ enum TECcodes : TERUnderlyingType {
// backward compatibility with historical data on non-prod networks, can be
// reclaimed after those networks reset.
tecNO_DELEGATE_PERMISSION = 198,
tecWASM_REJECTED = 199,
tecINVALID_PARAMETERS = 200,
};
//------------------------------------------------------------------------------

View File

@@ -212,8 +212,21 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TF_FLAG(tfLoanDefault, 0x00010000) \
TF_FLAG(tfLoanImpair, 0x00020000) \
TF_FLAG(tfLoanUnimpair, 0x00040000), \
MASK_ADJ(0)) \
\
TRANSACTION(Contract, \
TF_FLAG(tfImmutable, 0x00010000) \
TF_FLAG(tfCodeImmutable, 0x00020000) \
TF_FLAG(tfABIImmutable, 0x00040000) \
TF_FLAG(tfUndeletable, 0x00080000), \
MASK_ADJ(0))
constexpr std::uint32_t tfSendAmount = 0x00010000;
constexpr std::uint32_t tfSendNFToken = 0x00020000;
constexpr std::uint32_t tfAuthorizeToken = 0x00040000;
constexpr std::uint32_t tfContractParameterMask =
~(tfSendAmount | tfSendNFToken | tfAuthorizeToken);
// clang-format on
// Create all the flag values.

View File

@@ -84,6 +84,12 @@ public:
if (obj.isFieldPresent(sfParentBatchID))
parentBatchID_ = obj.getFieldH256(sfParentBatchID);
if (obj.isFieldPresent(sfGasUsed))
gasUsed_ = obj.getFieldU32(sfGasUsed);
if (obj.isFieldPresent(sfWasmReturnCode))
wasmReturnCode_ = obj.getFieldI32(sfWasmReturnCode);
}
std::optional<STAmount> const&
@@ -104,6 +110,30 @@ public:
parentBatchID_ = id;
}
void
setGasUsed(std::optional<std::uint32_t> const gasUsed)
{
gasUsed_ = gasUsed;
}
std::optional<std::uint32_t> const&
getGasUsed() const
{
return gasUsed_;
}
void
setWasmReturnCode(std::optional<std::int32_t> const wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
std::optional<std::int32_t> const&
getWasmReturnCode() const
{
return wasmReturnCode_;
}
private:
uint256 transactionID_;
std::uint32_t ledgerSeq_;
@@ -112,6 +142,8 @@ private:
std::optional<STAmount> deliveredAmount_;
std::optional<uint256> parentBatchID_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> wasmReturnCode_;
STArray nodes_;
};

View File

@@ -0,0 +1,19 @@
#if !defined(EMITABLE)
#error "undefined macro: EMITABLE"
#endif
/**
* EMITABLE(name, type, txType, value)
*
* This macro defines a permission:
* name: the name of the permission.
* type: the GranularPermissionType enum.
* txType: the corresponding TxType for this permission.
* value: the uint32 numeric value for the enum type.
*/
/** This removes the contract account the ability to set or remove deposit auth. */
EMITABLE(AccountDepositAuth, ttACCOUNT_SET, 65537)
// ** This removes the contract account the ability to set or remove disable master key. */
EMITABLE(AccountDisableMaster, ttACCOUNT_SET, 65538)

View File

@@ -15,6 +15,7 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FEATURE(SmartContract, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(SmartEscrow, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (Security3_1_3, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)

View File

@@ -150,6 +150,7 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
{sfAMMID, soeOPTIONAL}, // pseudo-account designator
{sfVaultID, soeOPTIONAL}, // pseudo-account designator
{sfLoanBrokerID, soeOPTIONAL}, // pseudo-account designator
{sfContractID, soeOPTIONAL}, // pseudo-account designator
}))
/** A ledger object which contains a list of object identifiers.
@@ -337,6 +338,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
{sfCondition, soeOPTIONAL},
{sfCancelAfter, soeOPTIONAL},
{sfFinishAfter, soeOPTIONAL},
{sfFinishFunction, soeOPTIONAL},
{sfData, soeOPTIONAL},
{sfSourceTag, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfOwnerNode, soeREQUIRED},
@@ -608,5 +611,45 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
{sfLoanScale, soeDEFAULT},
}))
/** A ledger object representing a contract source.
\sa keylet::contractSource
*/
LEDGER_ENTRY(ltCONTRACT_SOURCE, 0x0085, ContractSource, contract_source, ({
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
{sfContractHash, soeREQUIRED},
{sfContractCode, soeREQUIRED},
{sfFunctions, soeREQUIRED},
{sfInstanceParameters, soeOPTIONAL},
{sfReferenceCount, soeREQUIRED},
}))
/** A ledger object representing a contract.
\sa keylet::contract
*/
LEDGER_ENTRY(ltCONTRACT, 0x0086, Contract, contract, ({
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
{sfSequence, soeREQUIRED},
{sfOwnerNode, soeREQUIRED},
{sfOwner, soeREQUIRED},
{sfContractAccount, soeREQUIRED},
{sfContractHash, soeREQUIRED},
{sfInstanceParameterValues, soeOPTIONAL},
{sfURI, soeOPTIONAL},
}))
/** A ledger object representing a contract data.
\sa keylet::contractData
*/
LEDGER_ENTRY(ltCONTRACT_DATA, 0x0087, ContractData, contract_data, ({
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
{sfOwnerNode, soeREQUIRED},
{sfOwner, soeREQUIRED},
{sfContractAccount, soeREQUIRED},
{sfContractJson, soeREQUIRED},
}))
#undef EXPAND
#undef LEDGER_ENTRY_DUPLICATE

View File

@@ -22,9 +22,10 @@ TYPED_SFIELD(sfAssetScale, UINT8, 5)
// 8-bit integers (uncommon)
TYPED_SFIELD(sfTickSize, UINT8, 16)
TYPED_SFIELD(sfUNLModifyDisabling, UINT8, 17)
TYPED_SFIELD(sfHookResult, UINT8, 18)
// 18 unused
TYPED_SFIELD(sfWasLockingChainSend, UINT8, 19)
TYPED_SFIELD(sfWithdrawalPolicy, UINT8, 20)
TYPED_SFIELD(sfContractResult, UINT8, 21)
// 16-bit integers (common)
TYPED_SFIELD(sfLedgerEntryType, UINT16, 1, SField::sMD_Never)
@@ -36,10 +37,7 @@ TYPED_SFIELD(sfDiscountedFee, UINT16, 6)
// 16-bit integers (uncommon)
TYPED_SFIELD(sfVersion, UINT16, 16)
TYPED_SFIELD(sfHookStateChangeCount, UINT16, 17)
TYPED_SFIELD(sfHookEmitCount, UINT16, 18)
TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19)
TYPED_SFIELD(sfHookApiVersion, UINT16, 20)
// 17 to 20 unused
TYPED_SFIELD(sfLedgerFixType, UINT16, 21)
TYPED_SFIELD(sfManagementFeeRate, UINT16, 22) // 1/10 basis points (bips)
@@ -90,9 +88,7 @@ TYPED_SFIELD(sfTicketSequence, UINT32, 41)
TYPED_SFIELD(sfNFTokenTaxon, UINT32, 42)
TYPED_SFIELD(sfMintedNFTokens, UINT32, 43)
TYPED_SFIELD(sfBurnedNFTokens, UINT32, 44)
TYPED_SFIELD(sfHookStateCount, UINT32, 45)
TYPED_SFIELD(sfEmitGeneration, UINT32, 46)
// 47 reserved for Hooks
// 45 to 47 unused
TYPED_SFIELD(sfVoteWeight, UINT32, 48)
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
@@ -116,6 +112,9 @@ TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bi
TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 69)
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 70)
TYPED_SFIELD(sfGasPrice, UINT32, 71)
TYPED_SFIELD(sfComputationAllowance, UINT32, 72)
TYPED_SFIELD(sfGasUsed, UINT32, 73)
TYPED_SFIELD(sfParameterFlag, UINT32, 74)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -133,9 +132,7 @@ TYPED_SFIELD(sfNFTokenOfferNode, UINT64, 12)
TYPED_SFIELD(sfEmitBurden, UINT64, 13)
// 64-bit integers (uncommon)
TYPED_SFIELD(sfHookOn, UINT64, 16)
TYPED_SFIELD(sfHookInstructionCount, UINT64, 17)
TYPED_SFIELD(sfHookReturnCode, UINT64, 18)
// 16 to 18 unused
TYPED_SFIELD(sfReferenceCount, UINT64, 19)
TYPED_SFIELD(sfXChainClaimID, UINT64, 20)
TYPED_SFIELD(sfXChainAccountCreateCount, UINT64, 21)
@@ -195,10 +192,7 @@ TYPED_SFIELD(sfPreviousPageMin, UINT256, 26)
TYPED_SFIELD(sfNextPageMin, UINT256, 27)
TYPED_SFIELD(sfNFTokenBuyOffer, UINT256, 28)
TYPED_SFIELD(sfNFTokenSellOffer, UINT256, 29)
TYPED_SFIELD(sfHookStateKey, UINT256, 30)
TYPED_SFIELD(sfHookHash, UINT256, 31)
TYPED_SFIELD(sfHookNamespace, UINT256, 32)
TYPED_SFIELD(sfHookSetTxnID, UINT256, 33)
// 30 to 33 unused
TYPED_SFIELD(sfDomainID, UINT256, 34)
TYPED_SFIELD(sfVaultID, UINT256, 35,
SField::sMD_PseudoAccount | SField::sMD_Default)
@@ -206,6 +200,9 @@ TYPED_SFIELD(sfParentBatchID, UINT256, 36)
TYPED_SFIELD(sfLoanBrokerID, UINT256, 37,
SField::sMD_PseudoAccount | SField::sMD_Default)
TYPED_SFIELD(sfLoanID, UINT256, 38)
TYPED_SFIELD(sfContractHash, UINT256, 39)
TYPED_SFIELD(sfContractID, UINT256, 40,
SField::sMD_PseudoAccount | SField::sMD_Default)
// number (common)
TYPED_SFIELD(sfNumber, NUMBER, 1)
@@ -226,8 +223,9 @@ TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15, SField::sMD_NeedsAsset
TYPED_SFIELD(sfPeriodicPayment, NUMBER, 16)
TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::sMD_NeedsAsset | SField::sMD_Default)
// int32
// 32-bit signed (common)
TYPED_SFIELD(sfLoanScale, INT32, 1)
TYPED_SFIELD(sfWasmReturnCode, INT32, 2)
// currency amount (common)
TYPED_SFIELD(sfAmount, AMOUNT, 1)
@@ -249,15 +247,13 @@ TYPED_SFIELD(sfMinimumOffer, AMOUNT, 16)
TYPED_SFIELD(sfRippleEscrow, AMOUNT, 17)
TYPED_SFIELD(sfDeliveredAmount, AMOUNT, 18)
TYPED_SFIELD(sfNFTokenBrokerFee, AMOUNT, 19)
// Reserve 20 & 21 for Hooks.
// 20 to 21 unused
// currency amount (fees)
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)
@@ -289,16 +285,16 @@ TYPED_SFIELD(sfMasterSignature, VL, 18, SField::sMD_Default, SFi
TYPED_SFIELD(sfUNLModifyValidator, VL, 19)
TYPED_SFIELD(sfValidatorToDisable, VL, 20)
TYPED_SFIELD(sfValidatorToReEnable, VL, 21)
TYPED_SFIELD(sfHookStateData, VL, 22)
TYPED_SFIELD(sfHookReturnString, VL, 23)
TYPED_SFIELD(sfHookParameterName, VL, 24)
TYPED_SFIELD(sfHookParameterValue, VL, 25)
// 22 to 25 unused
TYPED_SFIELD(sfDIDDocument, VL, 26)
TYPED_SFIELD(sfData, VL, 27)
TYPED_SFIELD(sfAssetClass, VL, 28)
TYPED_SFIELD(sfProvider, VL, 29)
TYPED_SFIELD(sfMPTokenMetadata, VL, 30)
TYPED_SFIELD(sfCredentialType, VL, 31)
TYPED_SFIELD(sfFinishFunction, VL, 32)
TYPED_SFIELD(sfContractCode, VL, 33)
TYPED_SFIELD(sfFunctionName, VL, 34)
// account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
@@ -315,7 +311,7 @@ TYPED_SFIELD(sfHolder, ACCOUNT, 11)
TYPED_SFIELD(sfDelegate, ACCOUNT, 12)
// account (uncommon)
TYPED_SFIELD(sfHookAccount, ACCOUNT, 16)
// 16 unused
TYPED_SFIELD(sfOtherChainSource, ACCOUNT, 18)
TYPED_SFIELD(sfOtherChainDestination, ACCOUNT, 19)
TYPED_SFIELD(sfAttestationSignerAccount, ACCOUNT, 20)
@@ -325,6 +321,7 @@ TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23)
TYPED_SFIELD(sfSubject, ACCOUNT, 24)
TYPED_SFIELD(sfBorrower, ACCOUNT, 25)
TYPED_SFIELD(sfCounterparty, ACCOUNT, 26)
TYPED_SFIELD(sfContractAccount, ACCOUNT, 27)
// vector of 256-bit
TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::sMD_Never)
@@ -363,7 +360,7 @@ UNTYPED_SFIELD(sfMemo, OBJECT, 10)
UNTYPED_SFIELD(sfSignerEntry, OBJECT, 11)
UNTYPED_SFIELD(sfNFToken, OBJECT, 12)
UNTYPED_SFIELD(sfEmitDetails, OBJECT, 13)
UNTYPED_SFIELD(sfHook, OBJECT, 14)
// 14 unused
UNTYPED_SFIELD(sfPermission, OBJECT, 15)
// inner object (uncommon)
@@ -371,11 +368,7 @@ UNTYPED_SFIELD(sfSigner, OBJECT, 16)
// 17 unused
UNTYPED_SFIELD(sfMajority, OBJECT, 18)
UNTYPED_SFIELD(sfDisabledValidator, OBJECT, 19)
UNTYPED_SFIELD(sfEmittedTxn, OBJECT, 20)
UNTYPED_SFIELD(sfHookExecution, OBJECT, 21)
UNTYPED_SFIELD(sfHookDefinition, OBJECT, 22)
UNTYPED_SFIELD(sfHookParameter, OBJECT, 23)
UNTYPED_SFIELD(sfHookGrant, OBJECT, 24)
// 20 to 24 unused
UNTYPED_SFIELD(sfVoteEntry, OBJECT, 25)
UNTYPED_SFIELD(sfAuctionSlot, OBJECT, 26)
UNTYPED_SFIELD(sfAuthAccount, OBJECT, 27)
@@ -389,6 +382,10 @@ UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34)
UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35)
UNTYPED_SFIELD(sfBook, OBJECT, 36)
UNTYPED_SFIELD(sfCounterpartySignature, OBJECT, 37, SField::sMD_Default, SField::notSigning)
UNTYPED_SFIELD(sfFunction, OBJECT, 38)
UNTYPED_SFIELD(sfInstanceParameter, OBJECT, 39)
UNTYPED_SFIELD(sfInstanceParameterValue, OBJECT, 40)
UNTYPED_SFIELD(sfParameter, OBJECT, 41)
// array of objects (common)
// ARRAY/1 is reserved for end of array
@@ -402,16 +399,14 @@ UNTYPED_SFIELD(sfSufficient, ARRAY, 7)
UNTYPED_SFIELD(sfAffectedNodes, ARRAY, 8)
UNTYPED_SFIELD(sfMemos, ARRAY, 9)
UNTYPED_SFIELD(sfNFTokens, ARRAY, 10)
UNTYPED_SFIELD(sfHooks, ARRAY, 11)
// 11 unused
UNTYPED_SFIELD(sfVoteSlots, ARRAY, 12)
UNTYPED_SFIELD(sfAdditionalBooks, ARRAY, 13)
// array of objects (uncommon)
UNTYPED_SFIELD(sfMajorities, ARRAY, 16)
UNTYPED_SFIELD(sfDisabledValidators, ARRAY, 17)
UNTYPED_SFIELD(sfHookExecutions, ARRAY, 18)
UNTYPED_SFIELD(sfHookParameters, ARRAY, 19)
UNTYPED_SFIELD(sfHookGrants, ARRAY, 20)
// 18 to 20 unused
UNTYPED_SFIELD(sfXChainClaimAttestations, ARRAY, 21)
UNTYPED_SFIELD(sfXChainCreateAccountAttestations, ARRAY, 22)
// 23 unused
@@ -423,3 +418,16 @@ UNTYPED_SFIELD(sfAcceptedCredentials, ARRAY, 28)
UNTYPED_SFIELD(sfPermissions, ARRAY, 29)
UNTYPED_SFIELD(sfRawTransactions, ARRAY, 30)
UNTYPED_SFIELD(sfBatchSigners, ARRAY, 31, SField::sMD_Default, SField::notSigning)
UNTYPED_SFIELD(sfFunctions, ARRAY, 32)
UNTYPED_SFIELD(sfInstanceParameters, ARRAY, 33)
UNTYPED_SFIELD(sfInstanceParameterValues, ARRAY, 34)
UNTYPED_SFIELD(sfParameters, ARRAY, 35)
// data
TYPED_SFIELD(sfParameterValue, DATA, 1, SField::sMD_Default)
// data type
TYPED_SFIELD(sfParameterType, DATATYPE, 1)
// json
TYPED_SFIELD(sfContractJson, JSON, 1)

View File

@@ -3,7 +3,7 @@
#endif
/**
* TRANSACTION(tag, value, name, delegable, amendments, privileges, fields)
* TRANSACTION(tag, value, name, delegable, amendments, privileges, emitable, fields)
*
* To ease maintenance, you may replace any unneeded values with "..."
* e.g. #define TRANSACTION(tag, value, name, ...)
@@ -28,6 +28,7 @@ TRANSACTION(ttPAYMENT, 0, Payment,
Delegation::delegable,
uint256{},
createAcct,
Emittance::emitable,
({
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported},
@@ -48,13 +49,16 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfDestination, soeREQUIRED},
{sfDestinationTag, soeOPTIONAL},
{sfAmount, soeREQUIRED, soeMPTSupported},
{sfCondition, soeOPTIONAL},
{sfCancelAfter, soeOPTIONAL},
{sfFinishAfter, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfFinishFunction, soeOPTIONAL},
{sfData, soeOPTIONAL},
}))
/** This transaction type completes an existing escrow. */
@@ -65,12 +69,14 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfOwner, soeREQUIRED},
{sfOfferSequence, soeREQUIRED},
{sfFulfillment, soeOPTIONAL},
{sfCondition, soeOPTIONAL},
{sfCredentialIDs, soeOPTIONAL},
{sfComputationAllowance, soeOPTIONAL},
}))
@@ -82,6 +88,7 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet,
Delegation::notDelegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfEmailHash, soeOPTIONAL},
{sfWalletLocator, soeOPTIONAL},
@@ -103,6 +110,7 @@ TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfOwner, soeREQUIRED},
{sfOfferSequence, soeREQUIRED},
@@ -116,6 +124,7 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey,
Delegation::notDelegable,
uint256{},
noPriv,
Emittance::notEmitable,
({
{sfRegularKey, soeOPTIONAL},
}))
@@ -130,6 +139,7 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfTakerPays, soeREQUIRED},
{sfTakerGets, soeREQUIRED},
@@ -146,6 +156,7 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfOfferSequence, soeREQUIRED},
}))
@@ -160,6 +171,7 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfTicketCount, soeREQUIRED},
}))
@@ -176,6 +188,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
Delegation::notDelegable,
uint256{},
noPriv,
Emittance::notEmitable,
({
{sfSignerQuorum, soeREQUIRED},
{sfSignerEntries, soeOPTIONAL},
@@ -189,6 +202,7 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED},
@@ -206,6 +220,7 @@ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfChannel, soeREQUIRED},
{sfAmount, soeREQUIRED},
@@ -220,6 +235,7 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfChannel, soeREQUIRED},
{sfAmount, soeOPTIONAL},
@@ -237,6 +253,7 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfDestination, soeREQUIRED},
{sfSendMax, soeREQUIRED},
@@ -253,6 +270,7 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfCheckID, soeREQUIRED},
{sfAmount, soeOPTIONAL},
@@ -267,6 +285,7 @@ TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfCheckID, soeREQUIRED},
}))
@@ -279,6 +298,7 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth,
Delegation::delegable,
uint256{},
noPriv,
Emittance::notEmitable,
({
{sfAuthorize, soeOPTIONAL},
{sfUnauthorize, soeOPTIONAL},
@@ -294,6 +314,7 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfLimitAmount, soeOPTIONAL},
{sfQualityIn, soeOPTIONAL},
@@ -308,6 +329,7 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete,
Delegation::notDelegable,
uint256{},
mustDeleteAcct,
Emittance::notEmitable,
({
{sfDestination, soeREQUIRED},
{sfDestinationTag, soeOPTIONAL},
@@ -324,6 +346,7 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint,
Delegation::delegable,
uint256{},
changeNFTCounts,
Emittance::emitable,
({
{sfNFTokenTaxon, soeREQUIRED},
{sfTransferFee, soeOPTIONAL},
@@ -342,6 +365,7 @@ TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn,
Delegation::delegable,
uint256{},
changeNFTCounts,
Emittance::emitable,
({
{sfNFTokenID, soeREQUIRED},
{sfOwner, soeOPTIONAL},
@@ -355,6 +379,7 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfNFTokenID, soeREQUIRED},
{sfAmount, soeREQUIRED},
@@ -371,6 +396,7 @@ TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfNFTokenOffers, soeREQUIRED},
}))
@@ -383,6 +409,7 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer,
Delegation::delegable,
uint256{},
noPriv,
Emittance::emitable,
({
{sfNFTokenBuyOffer, soeOPTIONAL},
{sfNFTokenSellOffer, soeOPTIONAL},
@@ -397,6 +424,7 @@ TRANSACTION(ttCLAWBACK, 30, Clawback,
Delegation::delegable,
featureClawback,
noPriv,
Emittance::emitable,
({
{sfAmount, soeREQUIRED, soeMPTSupported},
{sfHolder, soeOPTIONAL},
@@ -410,6 +438,7 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
Delegation::delegable,
featureAMMClawback,
mayDeleteAcct | overrideFreeze,
Emittance::emitable,
({
{sfHolder, soeREQUIRED},
{sfAsset, soeREQUIRED},
@@ -425,6 +454,7 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate,
Delegation::delegable,
featureAMM,
createPseudoAcct,
Emittance::emitable,
({
{sfAmount, soeREQUIRED},
{sfAmount2, soeREQUIRED},
@@ -439,6 +469,7 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
Delegation::delegable,
featureAMM,
noPriv,
Emittance::emitable,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
@@ -457,6 +488,7 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw,
Delegation::delegable,
featureAMM,
mayDeleteAcct,
Emittance::emitable,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
@@ -474,6 +506,7 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote,
Delegation::delegable,
featureAMM,
noPriv,
Emittance::emitable,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
@@ -488,6 +521,7 @@ TRANSACTION(ttAMM_BID, 39, AMMBid,
Delegation::delegable,
featureAMM,
noPriv,
Emittance::emitable,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
@@ -504,6 +538,7 @@ TRANSACTION(ttAMM_DELETE, 40, AMMDelete,
Delegation::delegable,
featureAMM,
mustDeleteAcct,
Emittance::emitable,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
@@ -517,6 +552,7 @@ TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID,
Delegation::delegable,
featureXChainBridge,
noPriv,
Emittance::emitable,
({
{sfXChainBridge, soeREQUIRED},
{sfSignatureReward, soeREQUIRED},
@@ -528,6 +564,7 @@ TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit,
Delegation::delegable,
featureXChainBridge,
noPriv,
Emittance::emitable,
({
{sfXChainBridge, soeREQUIRED},
{sfXChainClaimID, soeREQUIRED},
@@ -540,6 +577,7 @@ TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim,
Delegation::delegable,
featureXChainBridge,
noPriv,
Emittance::emitable,
({
{sfXChainBridge, soeREQUIRED},
{sfXChainClaimID, soeREQUIRED},
@@ -553,6 +591,7 @@ TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit,
Delegation::delegable,
featureXChainBridge,
noPriv,
Emittance::emitable,
({
{sfXChainBridge, soeREQUIRED},
{sfDestination, soeREQUIRED},
@@ -565,6 +604,7 @@ TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation,
Delegation::delegable,
featureXChainBridge,
createAcct,
Emittance::emitable,
({
{sfXChainBridge, soeREQUIRED},
@@ -586,6 +626,7 @@ TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46,
Delegation::delegable,
featureXChainBridge,
createAcct,
Emittance::emitable,
({
{sfXChainBridge, soeREQUIRED},
@@ -607,6 +648,7 @@ TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge,
Delegation::delegable,
featureXChainBridge,
noPriv,
Emittance::emitable,
({
{sfXChainBridge, soeREQUIRED},
{sfSignatureReward, soeOPTIONAL},
@@ -618,6 +660,7 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge,
Delegation::delegable,
featureXChainBridge,
noPriv,
Emittance::emitable,
({
{sfXChainBridge, soeREQUIRED},
{sfSignatureReward, soeREQUIRED},
@@ -632,6 +675,7 @@ TRANSACTION(ttDID_SET, 49, DIDSet,
Delegation::delegable,
featureDID,
noPriv,
Emittance::emitable,
({
{sfDIDDocument, soeOPTIONAL},
{sfURI, soeOPTIONAL},
@@ -646,6 +690,7 @@ TRANSACTION(ttDID_DELETE, 50, DIDDelete,
Delegation::delegable,
featureDID,
noPriv,
Emittance::emitable,
({}))
/** This transaction type creates an Oracle instance */
@@ -656,6 +701,7 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet,
Delegation::delegable,
featurePriceOracle,
noPriv,
Emittance::emitable,
({
{sfOracleDocumentID, soeREQUIRED},
{sfProvider, soeOPTIONAL},
@@ -673,6 +719,7 @@ TRANSACTION(ttORACLE_DELETE, 52, OracleDelete,
Delegation::delegable,
featurePriceOracle,
noPriv,
Emittance::emitable,
({
{sfOracleDocumentID, soeREQUIRED},
}))
@@ -685,6 +732,7 @@ TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix,
Delegation::delegable,
fixNFTokenPageLinks,
noPriv,
Emittance::emitable,
({
{sfLedgerFixType, soeREQUIRED},
{sfOwner, soeOPTIONAL},
@@ -698,6 +746,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate,
Delegation::delegable,
featureMPTokensV1,
createMPTIssuance,
Emittance::emitable,
({
{sfAssetScale, soeOPTIONAL},
{sfTransferFee, soeOPTIONAL},
@@ -715,6 +764,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy,
Delegation::delegable,
featureMPTokensV1,
destroyMPTIssuance,
Emittance::emitable,
({
{sfMPTokenIssuanceID, soeREQUIRED},
}))
@@ -727,6 +777,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
Delegation::delegable,
featureMPTokensV1,
noPriv,
Emittance::emitable,
({
{sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeOPTIONAL},
@@ -744,6 +795,7 @@ TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize,
Delegation::delegable,
featureMPTokensV1,
mustAuthorizeMPT,
Emittance::emitable,
({
{sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeOPTIONAL},
@@ -757,6 +809,7 @@ TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate,
Delegation::delegable,
featureCredentials,
noPriv,
Emittance::emitable,
({
{sfSubject, soeREQUIRED},
{sfCredentialType, soeREQUIRED},
@@ -772,6 +825,7 @@ TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept,
Delegation::delegable,
featureCredentials,
noPriv,
Emittance::emitable,
({
{sfIssuer, soeREQUIRED},
{sfCredentialType, soeREQUIRED},
@@ -785,6 +839,7 @@ TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete,
Delegation::delegable,
featureCredentials,
noPriv,
Emittance::emitable,
({
{sfSubject, soeOPTIONAL},
{sfIssuer, soeOPTIONAL},
@@ -799,6 +854,7 @@ TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify,
Delegation::delegable,
featureDynamicNFT,
noPriv,
Emittance::emitable,
({
{sfNFTokenID, soeREQUIRED},
{sfOwner, soeOPTIONAL},
@@ -813,6 +869,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet,
Delegation::delegable,
featurePermissionedDomains,
noPriv,
Emittance::emitable,
({
{sfDomainID, soeOPTIONAL},
{sfAcceptedCredentials, soeREQUIRED},
@@ -826,6 +883,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete,
Delegation::delegable,
featurePermissionedDomains,
noPriv,
Emittance::emitable,
({
{sfDomainID, soeREQUIRED},
}))
@@ -838,6 +896,7 @@ TRANSACTION(ttDELEGATE_SET, 64, DelegateSet,
Delegation::notDelegable,
featurePermissionDelegationV1_1,
noPriv,
Emittance::notEmitable,
({
{sfAuthorize, soeREQUIRED},
{sfPermissions, soeREQUIRED},
@@ -851,6 +910,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
Delegation::notDelegable,
featureSingleAssetVault,
createPseudoAcct | createMPTIssuance | mustModifyVault,
Emittance::emitable,
({
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAssetsMaximum, soeOPTIONAL},
@@ -869,6 +929,7 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet,
Delegation::notDelegable,
featureSingleAssetVault,
mustModifyVault,
Emittance::emitable,
({
{sfVaultID, soeREQUIRED},
{sfAssetsMaximum, soeOPTIONAL},
@@ -884,6 +945,7 @@ TRANSACTION(ttVAULT_DELETE, 67, VaultDelete,
Delegation::notDelegable,
featureSingleAssetVault,
mustDeleteAcct | destroyMPTIssuance | mustModifyVault,
Emittance::emitable,
({
{sfVaultID, soeREQUIRED},
}))
@@ -896,6 +958,7 @@ TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit,
Delegation::notDelegable,
featureSingleAssetVault,
mayAuthorizeMPT | mustModifyVault,
Emittance::emitable,
({
{sfVaultID, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported},
@@ -909,6 +972,7 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw,
Delegation::notDelegable,
featureSingleAssetVault,
mayDeleteMPT | mayAuthorizeMPT | mustModifyVault,
Emittance::emitable,
({
{sfVaultID, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported},
@@ -924,6 +988,7 @@ TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback,
Delegation::notDelegable,
featureSingleAssetVault,
mayDeleteMPT | mustModifyVault,
Emittance::emitable,
({
{sfVaultID, soeREQUIRED},
{sfHolder, soeREQUIRED},
@@ -938,6 +1003,7 @@ TRANSACTION(ttBATCH, 71, Batch,
Delegation::notDelegable,
featureBatch,
noPriv,
Emittance::notEmitable,
({
{sfRawTransactions, soeREQUIRED},
{sfBatchSigners, soeOPTIONAL},
@@ -952,7 +1018,9 @@ TRANSACTION(ttBATCH, 71, Batch,
TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet,
Delegation::notDelegable,
featureLendingProtocol,
createPseudoAcct | mayAuthorizeMPT, ({
createPseudoAcct | mayAuthorizeMPT,
Emittance::emitable,
({
{sfVaultID, soeREQUIRED},
{sfLoanBrokerID, soeOPTIONAL},
{sfData, soeOPTIONAL},
@@ -969,7 +1037,9 @@ TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet,
TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete,
Delegation::notDelegable,
featureLendingProtocol,
mustDeleteAcct | mayAuthorizeMPT, ({
mustDeleteAcct | mayAuthorizeMPT,
Emittance::emitable,
({
{sfLoanBrokerID, soeREQUIRED},
}))
@@ -980,7 +1050,9 @@ TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete,
TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit,
Delegation::notDelegable,
featureLendingProtocol,
noPriv, ({
noPriv,
Emittance::emitable,
({
{sfLoanBrokerID, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported},
}))
@@ -992,7 +1064,9 @@ TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit,
TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw,
Delegation::notDelegable,
featureLendingProtocol,
mayAuthorizeMPT, ({
mayAuthorizeMPT,
Emittance::emitable,
({
{sfLoanBrokerID, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported},
{sfDestination, soeOPTIONAL},
@@ -1007,7 +1081,9 @@ TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw,
TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback,
Delegation::notDelegable,
featureLendingProtocol,
noPriv, ({
noPriv,
Emittance::emitable,
({
{sfLoanBrokerID, soeOPTIONAL},
{sfAmount, soeOPTIONAL, soeMPTSupported},
}))
@@ -1019,7 +1095,9 @@ TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback,
TRANSACTION(ttLOAN_SET, 80, LoanSet,
Delegation::notDelegable,
featureLendingProtocol,
mayAuthorizeMPT | mustModifyVault, ({
mayAuthorizeMPT | mustModifyVault,
Emittance::emitable,
({
{sfLoanBrokerID, soeREQUIRED},
{sfData, soeOPTIONAL},
{sfCounterparty, soeOPTIONAL},
@@ -1046,7 +1124,9 @@ TRANSACTION(ttLOAN_SET, 80, LoanSet,
TRANSACTION(ttLOAN_DELETE, 81, LoanDelete,
Delegation::notDelegable,
featureLendingProtocol,
noPriv, ({
noPriv,
Emittance::emitable,
({
{sfLoanID, soeREQUIRED},
}))
@@ -1060,7 +1140,9 @@ TRANSACTION(ttLOAN_MANAGE, 82, LoanManage,
// All of the LoanManage options will modify the vault, but the
// transaction can succeed without options, essentially making it
// a noop.
mayModifyVault, ({
mayModifyVault,
Emittance::emitable,
({
{sfLoanID, soeREQUIRED},
}))
@@ -1071,11 +1153,110 @@ TRANSACTION(ttLOAN_MANAGE, 82, LoanManage,
TRANSACTION(ttLOAN_PAY, 84, LoanPay,
Delegation::notDelegable,
featureLendingProtocol,
mayAuthorizeMPT | mustModifyVault, ({
mayAuthorizeMPT | mustModifyVault,
Emittance::emitable,
({
{sfLoanID, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported},
}))
/** This transaction type creates the smart contract. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/contract/ContractCreate.h>
#endif
TRANSACTION(ttCONTRACT_CREATE, 85, ContractCreate,
Delegation::delegable,
featureSmartContract,
createPseudoAcct,
Emittance::emitable,
({
{sfContractCode, soeOPTIONAL},
{sfContractHash, soeOPTIONAL},
{sfFunctions, soeOPTIONAL},
{sfInstanceParameters, soeOPTIONAL},
{sfInstanceParameterValues, soeOPTIONAL},
{sfURI, soeOPTIONAL},
}))
/** This transaction type modifies the smart contract. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/contract/ContractModify.h>
#endif
TRANSACTION(ttCONTRACT_MODIFY, 86, ContractModify,
Delegation::delegable,
featureSmartContract,
noPriv,
Emittance::emitable,
({
{sfContractAccount, soeOPTIONAL},
{sfOwner, soeOPTIONAL},
{sfContractCode, soeOPTIONAL},
{sfContractHash, soeOPTIONAL},
{sfFunctions, soeOPTIONAL},
{sfInstanceParameters, soeOPTIONAL},
{sfInstanceParameterValues, soeOPTIONAL},
{sfURI, soeOPTIONAL},
}))
/** This transaction type deletes the smart contract. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/contract/ContractDelete.h>
#endif
TRANSACTION(ttCONTRACT_DELETE, 87, ContractDelete,
Delegation::delegable,
featureSmartContract,
mustDeleteAcct,
Emittance::emitable,
({
{sfContractAccount, soeREQUIRED},
}))
/** This transaction type claws back funds from the contract. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/contract/ContractClawback.h>
#endif
TRANSACTION(ttCONTRACT_CLAWBACK, 88, ContractClawback,
Delegation::delegable,
featureSmartContract,
noPriv,
Emittance::emitable,
({
{sfContractAccount, soeOPTIONAL},
{sfAmount, soeREQUIRED, soeMPTSupported},
}))
/** This transaction type deletes user data. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/contract/ContractUserDelete.h>
#endif
TRANSACTION(ttCONTRACT_USER_DELETE, 89, ContractUserDelete,
Delegation::delegable,
featureSmartContract,
noPriv,
Emittance::notEmitable,
({
{sfContractAccount, soeREQUIRED},
{sfFunctionName, soeREQUIRED},
{sfParameters, soeOPTIONAL},
{sfComputationAllowance, soeREQUIRED},
}))
/** This transaction type calls the smart contract. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/contract/ContractCall.h>
#endif
TRANSACTION(ttCONTRACT_CALL, 90, ContractCall,
Delegation::delegable,
featureSmartContract,
noPriv,
Emittance::notEmitable,
({
{sfContractAccount, soeREQUIRED},
{sfFunctionName, soeREQUIRED},
{sfParameters, soeOPTIONAL},
{sfComputationAllowance, soeREQUIRED},
}))
/** This system-generated transaction type is used to update the status of the various amendments.
For details, see: https://xrpl.org/amendments.html
@@ -1087,6 +1268,7 @@ TRANSACTION(ttAMENDMENT, 100, EnableAmendment,
Delegation::notDelegable,
uint256{},
noPriv,
Emittance::notEmitable,
({
{sfLedgerSequence, soeREQUIRED},
{sfAmendment, soeREQUIRED},
@@ -1099,6 +1281,7 @@ TRANSACTION(ttFEE, 101, SetFee,
Delegation::notDelegable,
uint256{},
noPriv,
Emittance::notEmitable,
({
{sfLedgerSequence, soeOPTIONAL},
// Old version uses raw numbers
@@ -1124,6 +1307,7 @@ TRANSACTION(ttUNL_MODIFY, 102, UNLModify,
Delegation::notDelegable,
uint256{},
noPriv,
Emittance::notEmitable,
({
{sfUNLModifyDisabling, soeREQUIRED},
{sfLedgerSequence, soeREQUIRED},

View File

@@ -186,6 +186,7 @@ JSS(common); // out: RPC server_definitions
JSS(complete); // out: NetworkOPs, InboundLedger
JSS(complete_ledgers); // out: NetworkOPs, PeerImp
JSS(consensus); // out: NetworkOPs, LedgerConsensus
JSS(contract_account); // out: ContractInfo
JSS(converge_time); // out: NetworkOPs
JSS(converge_time_s); // out: NetworkOPs
JSS(cookie); // out: NetworkOPs
@@ -270,6 +271,8 @@ JSS(flags); // out: AccountOffers, NetworkOPs
JSS(forward); // in: AccountTx
JSS(freeze); // out: AccountLines
JSS(freeze_peer); // out: AccountLines
JSS(function); // in: ContractInfo
JSS(functions); // out: ContractInfo
JSS(deep_freeze); // out: AccountLines
JSS(deep_freeze_peer); // out: AccountLines
JSS(frozen_balances); // out: GatewayBalances
@@ -551,6 +554,7 @@ JSS(size); // out: get_aggregate_price
JSS(snapshot); // in: Subscribe
JSS(source_account); // in: PathRequest, RipplePathFind
JSS(source_amount); // in: PathRequest, RipplePathFind
JSS(source_code_uri); // out: ContractInfo
JSS(source_currencies); // in: PathRequest, RipplePathFind
JSS(source_tag); // out: AccountChannels
JSS(stand_alone); // out: NetworkOPs
@@ -648,6 +652,7 @@ JSS(url); // in/out: Subscribe, Unsubscribe
JSS(url_password); // in: Subscribe
JSS(url_username); // in: Subscribe
JSS(urlgravatar); //
JSS(user_data); // out: ContractInfo
JSS(username); // in: Subscribe
JSS(validated); // out: NetworkOPs, RPCHelpers, AccountTx*, Tx
JSS(validator_list_expires); // out: NetworkOps, ValidatorList

View File

@@ -7,7 +7,12 @@
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/STBitString.h>
#include <xrpl/protocol/STBlob.h>
#include <xrpl/protocol/STCurrency.h>
#include <xrpl/protocol/STData.h>
#include <xrpl/protocol/STDataType.h>
#include <xrpl/protocol/STInteger.h>
#include <xrpl/protocol/STIssue.h>
#include <xrpl/protocol/STJson.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/STObject.h>

View File

@@ -518,6 +518,30 @@ public:
{
return this->sle_->isFieldPresent(sfLoanBrokerID);
}
/**
* @brief Get sfContractID (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT256::type::value_type>
getContractID() const
{
if (hasContractID())
return this->sle_->at(sfContractID);
return std::nullopt;
}
/**
* @brief Check if sfContractID is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasContractID() const
{
return this->sle_->isFieldPresent(sfContractID);
}
};
/**
@@ -819,6 +843,17 @@ public:
return *this;
}
/**
* @brief Set sfContractID (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
AccountRootBuilder&
setContractID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfContractID] = value;
return *this;
}
/**
* @brief Build and return the completed AccountRoot wrapper.
* @param index The ledger entry index.

View File

@@ -0,0 +1,334 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::ledger_entries {
class ContractBuilder;
/**
* @brief Ledger Entry: Contract
*
* Type: ltCONTRACT (0x0086)
* RPC Name: contract
*
* Immutable wrapper around SLE providing type-safe field access.
* Use ContractBuilder to construct new ledger entries.
*/
class Contract : public LedgerEntryBase
{
public:
static constexpr LedgerEntryType entryType = ltCONTRACT;
/**
* @brief Construct a Contract ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Contract(std::shared_ptr<SLE const> sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
if (sle_->getType() != entryType)
{
throw std::runtime_error("Invalid ledger entry type for Contract");
}
}
// Ledger entry-specific field getters
/**
* @brief Get sfPreviousTxnID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getPreviousTxnID() const
{
return this->sle_->at(sfPreviousTxnID);
}
/**
* @brief Get sfPreviousTxnLgrSeq (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getPreviousTxnLgrSeq() const
{
return this->sle_->at(sfPreviousTxnLgrSeq);
}
/**
* @brief Get sfSequence (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getSequence() const
{
return this->sle_->at(sfSequence);
}
/**
* @brief Get sfOwnerNode (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getOwnerNode() const
{
return this->sle_->at(sfOwnerNode);
}
/**
* @brief Get sfOwner (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getOwner() const
{
return this->sle_->at(sfOwner);
}
/**
* @brief Get sfContractAccount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getContractAccount() const
{
return this->sle_->at(sfContractAccount);
}
/**
* @brief Get sfContractHash (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getContractHash() const
{
return this->sle_->at(sfContractHash);
}
/**
* @brief Get sfInstanceParameterValues (soeOPTIONAL)
* @note This is an untyped field (unknown).
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getInstanceParameterValues() const
{
if (this->sle_->isFieldPresent(sfInstanceParameterValues))
return this->sle_->getFieldArray(sfInstanceParameterValues);
return std::nullopt;
}
/**
* @brief Check if sfInstanceParameterValues is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasInstanceParameterValues() const
{
return this->sle_->isFieldPresent(sfInstanceParameterValues);
}
/**
* @brief Get sfURI (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getURI() const
{
if (hasURI())
return this->sle_->at(sfURI);
return std::nullopt;
}
/**
* @brief Check if sfURI is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasURI() const
{
return this->sle_->isFieldPresent(sfURI);
}
};
/**
* @brief Builder for Contract ledger entries.
*
* Provides a fluent interface for constructing ledger entries with method chaining.
* Uses Json::Value internally for flexible ledger entry construction.
* Inherits common field setters from LedgerEntryBuilderBase.
*/
class ContractBuilder : public LedgerEntryBuilderBase<ContractBuilder>
{
public:
/**
* @brief Construct a new ContractBuilder with required fields.
* @param previousTxnID The sfPreviousTxnID field value.
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
* @param sequence The sfSequence field value.
* @param ownerNode The sfOwnerNode field value.
* @param owner The sfOwner field value.
* @param contractAccount The sfContractAccount field value.
* @param contractHash The sfContractHash field value.
*/
ContractBuilder(std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_UINT32::type::value_type> const& sequence,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_ACCOUNT::type::value_type> const& owner,std::decay_t<typename SF_ACCOUNT::type::value_type> const& contractAccount,std::decay_t<typename SF_UINT256::type::value_type> const& contractHash)
: LedgerEntryBuilderBase<ContractBuilder>(ltCONTRACT)
{
setPreviousTxnID(previousTxnID);
setPreviousTxnLgrSeq(previousTxnLgrSeq);
setSequence(sequence);
setOwnerNode(ownerNode);
setOwner(owner);
setContractAccount(contractAccount);
setContractHash(contractHash);
}
/**
* @brief Construct a ContractBuilder from an existing SLE object.
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
ContractBuilder(std::shared_ptr<SLE const> sle)
{
if (sle->at(sfLedgerEntryType) != ltCONTRACT)
{
throw std::runtime_error("Invalid ledger entry type for Contract");
}
object_ = *sle;
}
/** @brief Ledger entry-specific field setters */
/**
* @brief Set sfPreviousTxnID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractBuilder&
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfPreviousTxnID] = value;
return *this;
}
/**
* @brief Set sfPreviousTxnLgrSeq (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractBuilder&
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfPreviousTxnLgrSeq] = value;
return *this;
}
/**
* @brief Set sfSequence (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractBuilder&
setSequence(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSequence] = value;
return *this;
}
/**
* @brief Set sfOwnerNode (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractBuilder&
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfOwnerNode] = value;
return *this;
}
/**
* @brief Set sfOwner (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractBuilder&
setOwner(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfOwner] = value;
return *this;
}
/**
* @brief Set sfContractAccount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractBuilder&
setContractAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfContractAccount] = value;
return *this;
}
/**
* @brief Set sfContractHash (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractBuilder&
setContractHash(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfContractHash] = value;
return *this;
}
/**
* @brief Set sfInstanceParameterValues (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractBuilder&
setInstanceParameterValues(STArray const& value)
{
object_.setFieldArray(sfInstanceParameterValues, value);
return *this;
}
/**
* @brief Set sfURI (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractBuilder&
setURI(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfURI] = value;
return *this;
}
/**
* @brief Build and return the completed Contract wrapper.
* @param index The ledger entry index.
* @return The constructed ledger entry wrapper.
*/
Contract
build(uint256 const& index)
{
return Contract{std::make_shared<SLE>(std::move(object_), index)};
}
};
} // namespace xrpl::ledger_entries

View File

@@ -0,0 +1,239 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::ledger_entries {
class ContractDataBuilder;
/**
* @brief Ledger Entry: ContractData
*
* Type: ltCONTRACT_DATA (0x0087)
* RPC Name: contract_data
*
* Immutable wrapper around SLE providing type-safe field access.
* Use ContractDataBuilder to construct new ledger entries.
*/
class ContractData : public LedgerEntryBase
{
public:
static constexpr LedgerEntryType entryType = ltCONTRACT_DATA;
/**
* @brief Construct a ContractData ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit ContractData(std::shared_ptr<SLE const> sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
if (sle_->getType() != entryType)
{
throw std::runtime_error("Invalid ledger entry type for ContractData");
}
}
// Ledger entry-specific field getters
/**
* @brief Get sfPreviousTxnID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getPreviousTxnID() const
{
return this->sle_->at(sfPreviousTxnID);
}
/**
* @brief Get sfPreviousTxnLgrSeq (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getPreviousTxnLgrSeq() const
{
return this->sle_->at(sfPreviousTxnLgrSeq);
}
/**
* @brief Get sfOwnerNode (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getOwnerNode() const
{
return this->sle_->at(sfOwnerNode);
}
/**
* @brief Get sfOwner (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getOwner() const
{
return this->sle_->at(sfOwner);
}
/**
* @brief Get sfContractAccount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getContractAccount() const
{
return this->sle_->at(sfContractAccount);
}
/**
* @brief Get sfContractJson (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_JSON::type::value_type
getContractJson() const
{
return this->sle_->at(sfContractJson);
}
};
/**
* @brief Builder for ContractData ledger entries.
*
* Provides a fluent interface for constructing ledger entries with method chaining.
* Uses Json::Value internally for flexible ledger entry construction.
* Inherits common field setters from LedgerEntryBuilderBase.
*/
class ContractDataBuilder : public LedgerEntryBuilderBase<ContractDataBuilder>
{
public:
/**
* @brief Construct a new ContractDataBuilder with required fields.
* @param previousTxnID The sfPreviousTxnID field value.
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
* @param ownerNode The sfOwnerNode field value.
* @param owner The sfOwner field value.
* @param contractAccount The sfContractAccount field value.
* @param contractJson The sfContractJson field value.
*/
ContractDataBuilder(std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_ACCOUNT::type::value_type> const& owner,std::decay_t<typename SF_ACCOUNT::type::value_type> const& contractAccount,std::decay_t<typename SF_JSON::type::value_type> const& contractJson)
: LedgerEntryBuilderBase<ContractDataBuilder>(ltCONTRACT_DATA)
{
setPreviousTxnID(previousTxnID);
setPreviousTxnLgrSeq(previousTxnLgrSeq);
setOwnerNode(ownerNode);
setOwner(owner);
setContractAccount(contractAccount);
setContractJson(contractJson);
}
/**
* @brief Construct a ContractDataBuilder from an existing SLE object.
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
ContractDataBuilder(std::shared_ptr<SLE const> sle)
{
if (sle->at(sfLedgerEntryType) != ltCONTRACT_DATA)
{
throw std::runtime_error("Invalid ledger entry type for ContractData");
}
object_ = *sle;
}
/** @brief Ledger entry-specific field setters */
/**
* @brief Set sfPreviousTxnID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractDataBuilder&
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfPreviousTxnID] = value;
return *this;
}
/**
* @brief Set sfPreviousTxnLgrSeq (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractDataBuilder&
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfPreviousTxnLgrSeq] = value;
return *this;
}
/**
* @brief Set sfOwnerNode (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractDataBuilder&
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfOwnerNode] = value;
return *this;
}
/**
* @brief Set sfOwner (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractDataBuilder&
setOwner(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfOwner] = value;
return *this;
}
/**
* @brief Set sfContractAccount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractDataBuilder&
setContractAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfContractAccount] = value;
return *this;
}
/**
* @brief Set sfContractJson (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractDataBuilder&
setContractJson(std::decay_t<typename SF_JSON::type::value_type> const& value)
{
object_[sfContractJson] = value;
return *this;
}
/**
* @brief Build and return the completed ContractData wrapper.
* @param index The ledger entry index.
* @return The constructed ledger entry wrapper.
*/
ContractData
build(uint256 const& index)
{
return ContractData{std::make_shared<SLE>(std::move(object_), index)};
}
};
} // namespace xrpl::ledger_entries

View File

@@ -0,0 +1,276 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::ledger_entries {
class ContractSourceBuilder;
/**
* @brief Ledger Entry: ContractSource
*
* Type: ltCONTRACT_SOURCE (0x0085)
* RPC Name: contract_source
*
* Immutable wrapper around SLE providing type-safe field access.
* Use ContractSourceBuilder to construct new ledger entries.
*/
class ContractSource : public LedgerEntryBase
{
public:
static constexpr LedgerEntryType entryType = ltCONTRACT_SOURCE;
/**
* @brief Construct a ContractSource ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit ContractSource(std::shared_ptr<SLE const> sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
if (sle_->getType() != entryType)
{
throw std::runtime_error("Invalid ledger entry type for ContractSource");
}
}
// Ledger entry-specific field getters
/**
* @brief Get sfPreviousTxnID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getPreviousTxnID() const
{
return this->sle_->at(sfPreviousTxnID);
}
/**
* @brief Get sfPreviousTxnLgrSeq (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getPreviousTxnLgrSeq() const
{
return this->sle_->at(sfPreviousTxnLgrSeq);
}
/**
* @brief Get sfContractHash (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getContractHash() const
{
return this->sle_->at(sfContractHash);
}
/**
* @brief Get sfContractCode (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getContractCode() const
{
return this->sle_->at(sfContractCode);
}
/**
* @brief Get sfFunctions (soeREQUIRED)
* @note This is an untyped field (unknown).
* @return The field value.
*/
[[nodiscard]]
STArray const&
getFunctions() const
{
return this->sle_->getFieldArray(sfFunctions);
}
/**
* @brief Get sfInstanceParameters (soeOPTIONAL)
* @note This is an untyped field (unknown).
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getInstanceParameters() const
{
if (this->sle_->isFieldPresent(sfInstanceParameters))
return this->sle_->getFieldArray(sfInstanceParameters);
return std::nullopt;
}
/**
* @brief Check if sfInstanceParameters is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasInstanceParameters() const
{
return this->sle_->isFieldPresent(sfInstanceParameters);
}
/**
* @brief Get sfReferenceCount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getReferenceCount() const
{
return this->sle_->at(sfReferenceCount);
}
};
/**
* @brief Builder for ContractSource ledger entries.
*
* Provides a fluent interface for constructing ledger entries with method chaining.
* Uses Json::Value internally for flexible ledger entry construction.
* Inherits common field setters from LedgerEntryBuilderBase.
*/
class ContractSourceBuilder : public LedgerEntryBuilderBase<ContractSourceBuilder>
{
public:
/**
* @brief Construct a new ContractSourceBuilder with required fields.
* @param previousTxnID The sfPreviousTxnID field value.
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
* @param contractHash The sfContractHash field value.
* @param contractCode The sfContractCode field value.
* @param functions The sfFunctions field value.
* @param referenceCount The sfReferenceCount field value.
*/
ContractSourceBuilder(std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_UINT256::type::value_type> const& contractHash,std::decay_t<typename SF_VL::type::value_type> const& contractCode,STArray const& functions,std::decay_t<typename SF_UINT64::type::value_type> const& referenceCount)
: LedgerEntryBuilderBase<ContractSourceBuilder>(ltCONTRACT_SOURCE)
{
setPreviousTxnID(previousTxnID);
setPreviousTxnLgrSeq(previousTxnLgrSeq);
setContractHash(contractHash);
setContractCode(contractCode);
setFunctions(functions);
setReferenceCount(referenceCount);
}
/**
* @brief Construct a ContractSourceBuilder from an existing SLE object.
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
ContractSourceBuilder(std::shared_ptr<SLE const> sle)
{
if (sle->at(sfLedgerEntryType) != ltCONTRACT_SOURCE)
{
throw std::runtime_error("Invalid ledger entry type for ContractSource");
}
object_ = *sle;
}
/** @brief Ledger entry-specific field setters */
/**
* @brief Set sfPreviousTxnID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractSourceBuilder&
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfPreviousTxnID] = value;
return *this;
}
/**
* @brief Set sfPreviousTxnLgrSeq (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractSourceBuilder&
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfPreviousTxnLgrSeq] = value;
return *this;
}
/**
* @brief Set sfContractHash (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractSourceBuilder&
setContractHash(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfContractHash] = value;
return *this;
}
/**
* @brief Set sfContractCode (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractSourceBuilder&
setContractCode(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfContractCode] = value;
return *this;
}
/**
* @brief Set sfFunctions (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractSourceBuilder&
setFunctions(STArray const& value)
{
object_.setFieldArray(sfFunctions, value);
return *this;
}
/**
* @brief Set sfInstanceParameters (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractSourceBuilder&
setInstanceParameters(STArray const& value)
{
object_.setFieldArray(sfInstanceParameters, value);
return *this;
}
/**
* @brief Set sfReferenceCount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractSourceBuilder&
setReferenceCount(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfReferenceCount] = value;
return *this;
}
/**
* @brief Build and return the completed ContractSource wrapper.
* @param index The ledger entry index.
* @return The constructed ledger entry wrapper.
*/
ContractSource
build(uint256 const& index)
{
return ContractSource{std::make_shared<SLE>(std::move(object_), index)};
}
};
} // namespace xrpl::ledger_entries

View File

@@ -174,6 +174,54 @@ public:
return this->sle_->isFieldPresent(sfFinishAfter);
}
/**
* @brief Get sfFinishFunction (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getFinishFunction() const
{
if (hasFinishFunction())
return this->sle_->at(sfFinishFunction);
return std::nullopt;
}
/**
* @brief Check if sfFinishFunction is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasFinishFunction() const
{
return this->sle_->isFieldPresent(sfFinishFunction);
}
/**
* @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.
@@ -451,6 +499,28 @@ public:
return *this;
}
/**
* @brief Set sfFinishFunction (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
EscrowBuilder&
setFinishFunction(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfFinishFunction] = 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

@@ -0,0 +1,212 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ContractCallBuilder;
/**
* @brief Transaction: ContractCall
*
* Type: ttCONTRACT_CALL (90)
* Delegable: Delegation::delegable
* Amendment: featureSmartContract
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ContractCallBuilder to construct new transactions.
*/
class ContractCall : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONTRACT_CALL;
/**
* @brief Construct a ContractCall transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ContractCall(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ContractCall");
}
}
// Transaction-specific field getters
/**
* @brief Get sfContractAccount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getContractAccount() const
{
return this->tx_->at(sfContractAccount);
}
/**
* @brief Get sfFunctionName (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getFunctionName() const
{
return this->tx_->at(sfFunctionName);
}
/**
* @brief Get sfParameters (soeOPTIONAL)
* @note This is an untyped field.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getParameters() const
{
if (this->tx_->isFieldPresent(sfParameters))
return this->tx_->getFieldArray(sfParameters);
return std::nullopt;
}
/**
* @brief Check if sfParameters is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasParameters() const
{
return this->tx_->isFieldPresent(sfParameters);
}
/**
* @brief Get sfComputationAllowance (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getComputationAllowance() const
{
return this->tx_->at(sfComputationAllowance);
}
};
/**
* @brief Builder for ContractCall transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ContractCallBuilder : public TransactionBuilderBase<ContractCallBuilder>
{
public:
/**
* @brief Construct a new ContractCallBuilder with required fields.
* @param account The account initiating the transaction.
* @param contractAccount The sfContractAccount field value.
* @param functionName The sfFunctionName field value.
* @param computationAllowance The sfComputationAllowance field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ContractCallBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_ACCOUNT::type::value_type> const& contractAccount, std::decay_t<typename SF_VL::type::value_type> const& functionName, std::decay_t<typename SF_UINT32::type::value_type> const& computationAllowance, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ContractCallBuilder>(ttCONTRACT_CALL, account, sequence, fee)
{
setContractAccount(contractAccount);
setFunctionName(functionName);
setComputationAllowance(computationAllowance);
}
/**
* @brief Construct a ContractCallBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ContractCallBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONTRACT_CALL)
{
throw std::runtime_error("Invalid transaction type for ContractCallBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfContractAccount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractCallBuilder&
setContractAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfContractAccount] = value;
return *this;
}
/**
* @brief Set sfFunctionName (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractCallBuilder&
setFunctionName(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfFunctionName] = value;
return *this;
}
/**
* @brief Set sfParameters (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractCallBuilder&
setParameters(STArray const& value)
{
object_.setFieldArray(sfParameters, value);
return *this;
}
/**
* @brief Set sfComputationAllowance (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractCallBuilder&
setComputationAllowance(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfComputationAllowance] = value;
return *this;
}
/**
* @brief Build and return the ContractCall wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ContractCall
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ContractCall{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,168 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ContractClawbackBuilder;
/**
* @brief Transaction: ContractClawback
*
* Type: ttCONTRACT_CLAWBACK (88)
* Delegable: Delegation::delegable
* Amendment: featureSmartContract
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ContractClawbackBuilder to construct new transactions.
*/
class ContractClawback : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONTRACT_CLAWBACK;
/**
* @brief Construct a ContractClawback transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ContractClawback(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ContractClawback");
}
}
// Transaction-specific field getters
/**
* @brief Get sfContractAccount (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getContractAccount() const
{
if (hasContractAccount())
{
return this->tx_->at(sfContractAccount);
}
return std::nullopt;
}
/**
* @brief Check if sfContractAccount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasContractAccount() const
{
return this->tx_->isFieldPresent(sfContractAccount);
}
/**
* @brief Get sfAmount (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
SF_AMOUNT::type::value_type
getAmount() const
{
return this->tx_->at(sfAmount);
}
};
/**
* @brief Builder for ContractClawback transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ContractClawbackBuilder : public TransactionBuilderBase<ContractClawbackBuilder>
{
public:
/**
* @brief Construct a new ContractClawbackBuilder with required fields.
* @param account The account initiating the transaction.
* @param amount The sfAmount field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ContractClawbackBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_AMOUNT::type::value_type> const& amount, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ContractClawbackBuilder>(ttCONTRACT_CLAWBACK, account, sequence, fee)
{
setAmount(amount);
}
/**
* @brief Construct a ContractClawbackBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ContractClawbackBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONTRACT_CLAWBACK)
{
throw std::runtime_error("Invalid transaction type for ContractClawbackBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfContractAccount (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractClawbackBuilder&
setContractAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfContractAccount] = value;
return *this;
}
/**
* @brief Set sfAmount (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
ContractClawbackBuilder&
setAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfAmount] = value;
return *this;
}
/**
* @brief Build and return the ContractClawback wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ContractClawback
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ContractClawback{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,321 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ContractCreateBuilder;
/**
* @brief Transaction: ContractCreate
*
* Type: ttCONTRACT_CREATE (85)
* Delegable: Delegation::delegable
* Amendment: featureSmartContract
* Privileges: createPseudoAcct
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ContractCreateBuilder to construct new transactions.
*/
class ContractCreate : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONTRACT_CREATE;
/**
* @brief Construct a ContractCreate transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ContractCreate(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ContractCreate");
}
}
// Transaction-specific field getters
/**
* @brief Get sfContractCode (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getContractCode() const
{
if (hasContractCode())
{
return this->tx_->at(sfContractCode);
}
return std::nullopt;
}
/**
* @brief Check if sfContractCode is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasContractCode() const
{
return this->tx_->isFieldPresent(sfContractCode);
}
/**
* @brief Get sfContractHash (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT256::type::value_type>
getContractHash() const
{
if (hasContractHash())
{
return this->tx_->at(sfContractHash);
}
return std::nullopt;
}
/**
* @brief Check if sfContractHash is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasContractHash() const
{
return this->tx_->isFieldPresent(sfContractHash);
}
/**
* @brief Get sfFunctions (soeOPTIONAL)
* @note This is an untyped field.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getFunctions() const
{
if (this->tx_->isFieldPresent(sfFunctions))
return this->tx_->getFieldArray(sfFunctions);
return std::nullopt;
}
/**
* @brief Check if sfFunctions is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasFunctions() const
{
return this->tx_->isFieldPresent(sfFunctions);
}
/**
* @brief Get sfInstanceParameters (soeOPTIONAL)
* @note This is an untyped field.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getInstanceParameters() const
{
if (this->tx_->isFieldPresent(sfInstanceParameters))
return this->tx_->getFieldArray(sfInstanceParameters);
return std::nullopt;
}
/**
* @brief Check if sfInstanceParameters is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasInstanceParameters() const
{
return this->tx_->isFieldPresent(sfInstanceParameters);
}
/**
* @brief Get sfInstanceParameterValues (soeOPTIONAL)
* @note This is an untyped field.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getInstanceParameterValues() const
{
if (this->tx_->isFieldPresent(sfInstanceParameterValues))
return this->tx_->getFieldArray(sfInstanceParameterValues);
return std::nullopt;
}
/**
* @brief Check if sfInstanceParameterValues is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasInstanceParameterValues() const
{
return this->tx_->isFieldPresent(sfInstanceParameterValues);
}
/**
* @brief Get sfURI (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getURI() const
{
if (hasURI())
{
return this->tx_->at(sfURI);
}
return std::nullopt;
}
/**
* @brief Check if sfURI is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasURI() const
{
return this->tx_->isFieldPresent(sfURI);
}
};
/**
* @brief Builder for ContractCreate transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ContractCreateBuilder : public TransactionBuilderBase<ContractCreateBuilder>
{
public:
/**
* @brief Construct a new ContractCreateBuilder with required fields.
* @param account The account initiating the transaction.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ContractCreateBuilder(SF_ACCOUNT::type::value_type account,
std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ContractCreateBuilder>(ttCONTRACT_CREATE, account, sequence, fee)
{
}
/**
* @brief Construct a ContractCreateBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ContractCreateBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONTRACT_CREATE)
{
throw std::runtime_error("Invalid transaction type for ContractCreateBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfContractCode (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractCreateBuilder&
setContractCode(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfContractCode] = value;
return *this;
}
/**
* @brief Set sfContractHash (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractCreateBuilder&
setContractHash(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfContractHash] = value;
return *this;
}
/**
* @brief Set sfFunctions (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractCreateBuilder&
setFunctions(STArray const& value)
{
object_.setFieldArray(sfFunctions, value);
return *this;
}
/**
* @brief Set sfInstanceParameters (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractCreateBuilder&
setInstanceParameters(STArray const& value)
{
object_.setFieldArray(sfInstanceParameters, value);
return *this;
}
/**
* @brief Set sfInstanceParameterValues (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractCreateBuilder&
setInstanceParameterValues(STArray const& value)
{
object_.setFieldArray(sfInstanceParameterValues, value);
return *this;
}
/**
* @brief Set sfURI (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractCreateBuilder&
setURI(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfURI] = value;
return *this;
}
/**
* @brief Build and return the ContractCreate wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ContractCreate
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ContractCreate{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,129 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ContractDeleteBuilder;
/**
* @brief Transaction: ContractDelete
*
* Type: ttCONTRACT_DELETE (87)
* Delegable: Delegation::delegable
* Amendment: featureSmartContract
* Privileges: mustDeleteAcct
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ContractDeleteBuilder to construct new transactions.
*/
class ContractDelete : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONTRACT_DELETE;
/**
* @brief Construct a ContractDelete transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ContractDelete(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ContractDelete");
}
}
// Transaction-specific field getters
/**
* @brief Get sfContractAccount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getContractAccount() const
{
return this->tx_->at(sfContractAccount);
}
};
/**
* @brief Builder for ContractDelete transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ContractDeleteBuilder : public TransactionBuilderBase<ContractDeleteBuilder>
{
public:
/**
* @brief Construct a new ContractDeleteBuilder with required fields.
* @param account The account initiating the transaction.
* @param contractAccount The sfContractAccount field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ContractDeleteBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_ACCOUNT::type::value_type> const& contractAccount, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ContractDeleteBuilder>(ttCONTRACT_DELETE, account, sequence, fee)
{
setContractAccount(contractAccount);
}
/**
* @brief Construct a ContractDeleteBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ContractDeleteBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONTRACT_DELETE)
{
throw std::runtime_error("Invalid transaction type for ContractDeleteBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfContractAccount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractDeleteBuilder&
setContractAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfContractAccount] = value;
return *this;
}
/**
* @brief Build and return the ContractDelete wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ContractDelete
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ContractDelete{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,395 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ContractModifyBuilder;
/**
* @brief Transaction: ContractModify
*
* Type: ttCONTRACT_MODIFY (86)
* Delegable: Delegation::delegable
* Amendment: featureSmartContract
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ContractModifyBuilder to construct new transactions.
*/
class ContractModify : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONTRACT_MODIFY;
/**
* @brief Construct a ContractModify transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ContractModify(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ContractModify");
}
}
// Transaction-specific field getters
/**
* @brief Get sfContractAccount (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getContractAccount() const
{
if (hasContractAccount())
{
return this->tx_->at(sfContractAccount);
}
return std::nullopt;
}
/**
* @brief Check if sfContractAccount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasContractAccount() const
{
return this->tx_->isFieldPresent(sfContractAccount);
}
/**
* @brief Get sfOwner (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getOwner() const
{
if (hasOwner())
{
return this->tx_->at(sfOwner);
}
return std::nullopt;
}
/**
* @brief Check if sfOwner is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasOwner() const
{
return this->tx_->isFieldPresent(sfOwner);
}
/**
* @brief Get sfContractCode (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getContractCode() const
{
if (hasContractCode())
{
return this->tx_->at(sfContractCode);
}
return std::nullopt;
}
/**
* @brief Check if sfContractCode is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasContractCode() const
{
return this->tx_->isFieldPresent(sfContractCode);
}
/**
* @brief Get sfContractHash (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT256::type::value_type>
getContractHash() const
{
if (hasContractHash())
{
return this->tx_->at(sfContractHash);
}
return std::nullopt;
}
/**
* @brief Check if sfContractHash is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasContractHash() const
{
return this->tx_->isFieldPresent(sfContractHash);
}
/**
* @brief Get sfFunctions (soeOPTIONAL)
* @note This is an untyped field.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getFunctions() const
{
if (this->tx_->isFieldPresent(sfFunctions))
return this->tx_->getFieldArray(sfFunctions);
return std::nullopt;
}
/**
* @brief Check if sfFunctions is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasFunctions() const
{
return this->tx_->isFieldPresent(sfFunctions);
}
/**
* @brief Get sfInstanceParameters (soeOPTIONAL)
* @note This is an untyped field.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getInstanceParameters() const
{
if (this->tx_->isFieldPresent(sfInstanceParameters))
return this->tx_->getFieldArray(sfInstanceParameters);
return std::nullopt;
}
/**
* @brief Check if sfInstanceParameters is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasInstanceParameters() const
{
return this->tx_->isFieldPresent(sfInstanceParameters);
}
/**
* @brief Get sfInstanceParameterValues (soeOPTIONAL)
* @note This is an untyped field.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getInstanceParameterValues() const
{
if (this->tx_->isFieldPresent(sfInstanceParameterValues))
return this->tx_->getFieldArray(sfInstanceParameterValues);
return std::nullopt;
}
/**
* @brief Check if sfInstanceParameterValues is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasInstanceParameterValues() const
{
return this->tx_->isFieldPresent(sfInstanceParameterValues);
}
/**
* @brief Get sfURI (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getURI() const
{
if (hasURI())
{
return this->tx_->at(sfURI);
}
return std::nullopt;
}
/**
* @brief Check if sfURI is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasURI() const
{
return this->tx_->isFieldPresent(sfURI);
}
};
/**
* @brief Builder for ContractModify transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ContractModifyBuilder : public TransactionBuilderBase<ContractModifyBuilder>
{
public:
/**
* @brief Construct a new ContractModifyBuilder with required fields.
* @param account The account initiating the transaction.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ContractModifyBuilder(SF_ACCOUNT::type::value_type account,
std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ContractModifyBuilder>(ttCONTRACT_MODIFY, account, sequence, fee)
{
}
/**
* @brief Construct a ContractModifyBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ContractModifyBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONTRACT_MODIFY)
{
throw std::runtime_error("Invalid transaction type for ContractModifyBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfContractAccount (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractModifyBuilder&
setContractAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfContractAccount] = value;
return *this;
}
/**
* @brief Set sfOwner (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractModifyBuilder&
setOwner(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfOwner] = value;
return *this;
}
/**
* @brief Set sfContractCode (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractModifyBuilder&
setContractCode(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfContractCode] = value;
return *this;
}
/**
* @brief Set sfContractHash (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractModifyBuilder&
setContractHash(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfContractHash] = value;
return *this;
}
/**
* @brief Set sfFunctions (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractModifyBuilder&
setFunctions(STArray const& value)
{
object_.setFieldArray(sfFunctions, value);
return *this;
}
/**
* @brief Set sfInstanceParameters (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractModifyBuilder&
setInstanceParameters(STArray const& value)
{
object_.setFieldArray(sfInstanceParameters, value);
return *this;
}
/**
* @brief Set sfInstanceParameterValues (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractModifyBuilder&
setInstanceParameterValues(STArray const& value)
{
object_.setFieldArray(sfInstanceParameterValues, value);
return *this;
}
/**
* @brief Set sfURI (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractModifyBuilder&
setURI(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfURI] = value;
return *this;
}
/**
* @brief Build and return the ContractModify wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ContractModify
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ContractModify{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,212 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ContractUserDeleteBuilder;
/**
* @brief Transaction: ContractUserDelete
*
* Type: ttCONTRACT_USER_DELETE (89)
* Delegable: Delegation::delegable
* Amendment: featureSmartContract
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ContractUserDeleteBuilder to construct new transactions.
*/
class ContractUserDelete : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONTRACT_USER_DELETE;
/**
* @brief Construct a ContractUserDelete transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ContractUserDelete(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ContractUserDelete");
}
}
// Transaction-specific field getters
/**
* @brief Get sfContractAccount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getContractAccount() const
{
return this->tx_->at(sfContractAccount);
}
/**
* @brief Get sfFunctionName (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getFunctionName() const
{
return this->tx_->at(sfFunctionName);
}
/**
* @brief Get sfParameters (soeOPTIONAL)
* @note This is an untyped field.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getParameters() const
{
if (this->tx_->isFieldPresent(sfParameters))
return this->tx_->getFieldArray(sfParameters);
return std::nullopt;
}
/**
* @brief Check if sfParameters is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasParameters() const
{
return this->tx_->isFieldPresent(sfParameters);
}
/**
* @brief Get sfComputationAllowance (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getComputationAllowance() const
{
return this->tx_->at(sfComputationAllowance);
}
};
/**
* @brief Builder for ContractUserDelete transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ContractUserDeleteBuilder : public TransactionBuilderBase<ContractUserDeleteBuilder>
{
public:
/**
* @brief Construct a new ContractUserDeleteBuilder with required fields.
* @param account The account initiating the transaction.
* @param contractAccount The sfContractAccount field value.
* @param functionName The sfFunctionName field value.
* @param computationAllowance The sfComputationAllowance field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ContractUserDeleteBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_ACCOUNT::type::value_type> const& contractAccount, std::decay_t<typename SF_VL::type::value_type> const& functionName, std::decay_t<typename SF_UINT32::type::value_type> const& computationAllowance, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ContractUserDeleteBuilder>(ttCONTRACT_USER_DELETE, account, sequence, fee)
{
setContractAccount(contractAccount);
setFunctionName(functionName);
setComputationAllowance(computationAllowance);
}
/**
* @brief Construct a ContractUserDeleteBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ContractUserDeleteBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONTRACT_USER_DELETE)
{
throw std::runtime_error("Invalid transaction type for ContractUserDeleteBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfContractAccount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractUserDeleteBuilder&
setContractAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfContractAccount] = value;
return *this;
}
/**
* @brief Set sfFunctionName (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractUserDeleteBuilder&
setFunctionName(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfFunctionName] = value;
return *this;
}
/**
* @brief Set sfParameters (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ContractUserDeleteBuilder&
setParameters(STArray const& value)
{
object_.setFieldArray(sfParameters, value);
return *this;
}
/**
* @brief Set sfComputationAllowance (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ContractUserDeleteBuilder&
setComputationAllowance(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfComputationAllowance] = value;
return *this;
}
/**
* @brief Build and return the ContractUserDelete wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ContractUserDelete
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ContractUserDelete{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

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 sfFinishFunction (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>
getFinishFunction() const
{
if (hasDestinationTag())
if (hasFinishFunction())
{
return this->tx_->at(sfDestinationTag);
return this->tx_->at(sfFinishFunction);
}
return std::nullopt;
}
/**
* @brief Check if sfDestinationTag is present.
* @brief Check if sfFinishFunction is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasDestinationTag() const
hasFinishFunction() const
{
return this->tx_->isFieldPresent(sfDestinationTag);
return this->tx_->isFieldPresent(sfFinishFunction);
}
/**
* @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);
}
};
@@ -230,6 +282,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.
@@ -276,13 +339,24 @@ public:
}
/**
* @brief Set sfDestinationTag (soeOPTIONAL)
* @brief Set sfFinishFunction (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
EscrowCreateBuilder&
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
setFinishFunction(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfDestinationTag] = value;
object_[sfFinishFunction] = 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 sfComputationAllowance (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getComputationAllowance() const
{
if (hasComputationAllowance())
{
return this->tx_->at(sfComputationAllowance);
}
return std::nullopt;
}
/**
* @brief Check if sfComputationAllowance is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasComputationAllowance() const
{
return this->tx_->isFieldPresent(sfComputationAllowance);
}
};
/**
@@ -247,6 +273,17 @@ public:
return *this;
}
/**
* @brief Set sfComputationAllowance (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
EscrowFinishBuilder&
setComputationAllowance(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfComputationAllowance] = value;
return *this;
}
/**
* @brief Build and return the EscrowFinish wrapper.
* @param publicKey The public key for signing.

View File

@@ -148,6 +148,11 @@ public:
virtual bool
unsubConsensus(std::uint64_t uListener) = 0;
virtual bool
subContractEvent(ref ispListener) = 0;
virtual bool
unsubContractEvent(std::uint64_t uListener) = 0;
// VFALCO TODO Remove
// This was added for one particular partner, it
// "pushes" subscription data to a particular URL.

View File

@@ -247,6 +247,9 @@ public:
virtual void
pubValidation(std::shared_ptr<STValidation> const& val) = 0;
virtual void
pubContractEvent(std::string const& name, STJson const& event) = 0;
virtual void
stateAccounting(Json::Value& obj) = 0;
};

View File

@@ -3,10 +3,12 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyViewImpl.h>
#include <xrpl/ledger/OpenViewSandbox.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/XRPAmount.h>
#include <optional>
#include <queue>
namespace xrpl {
@@ -43,6 +45,12 @@ public:
XRPAmount const baseFee;
beast::Journal const journal;
OpenView&
openView()
{
return base_.view();
}
ApplyView&
view()
{
@@ -75,10 +83,41 @@ 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
setWasmReturnCode(std::int32_t const wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
/** Sets the gas used in the metadata */
void
setEmittedTxns(std::queue<std::shared_ptr<STTx const>> const emittedTxns)
{
emittedTxns_ = emittedTxns;
}
std::queue<std::shared_ptr<STTx const>>
getEmittedTxns()
{
return emittedTxns_;
}
/** Discard changes and start fresh. */
void
discard();
/** Finalize changes. */
void
finalize();
/** Apply the transaction result to the base. */
std::optional<TxMeta> apply(TER);
@@ -118,11 +157,13 @@ private:
TER
checkInvariantsHelper(TER const result, XRPAmount const fee, std::index_sequence<Is...>);
OpenView& base_;
OpenViewSandbox base_;
ApplyFlags flags_;
std::optional<ApplyViewImpl> view_;
// The ID of the batch transaction we are executing under, if seated.
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> wasmReturnCode_;
std::queue<std::shared_ptr<STTx const>> emittedTxns_;
std::optional<uint256 const> parentBatchId_;
};

View File

@@ -297,6 +297,9 @@ private:
std::pair<TER, XRPAmount>
reset(XRPAmount fee);
std::pair<TER, XRPAmount>
checkInvariants(TER result, XRPAmount fee);
TER
consumeSeqProxy(SLE::pointer const& sleAccount);
TER

View File

@@ -9,6 +9,8 @@
namespace xrpl {
class Application;
class HashRouter;
class ServiceRegistry;
@@ -102,6 +104,24 @@ apply(
ApplyFlags flags,
beast::Journal journal);
ApplyResult
apply(
ServiceRegistry& registry,
OpenView& view,
uint256 const& parentBatchId,
STTx const& tx,
ApplyFlags flags,
beast::Journal j);
ApplyResult
apply(
Application& app,
OpenView& view,
uint256 const& parentBatchId,
STTx const& tx,
ApplyFlags flags,
beast::Journal j);
/** Enum class for return value from `applyTransaction`
@see applyTransaction

View File

@@ -0,0 +1,42 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
// Define a function pointer type that can be used to delete ledger node types.
using DeleterFuncPtr = TER (*)(
ServiceRegistry& registry,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j);
DeleterFuncPtr
nonObligationDeleter(LedgerEntryType t);
TER
deletePreclaim(
PreclaimContext const& ctx,
std::uint32_t seqDelta,
AccountID const account,
AccountID const dest,
bool isPseudoAccount = false);
TER
deleteDoApply(
ApplyContext& applyCtx,
STAmount const& accountBalance,
AccountID const& account,
AccountID const& dest);
} // namespace xrpl

View File

@@ -0,0 +1,29 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class ContractCall : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ContractCall(ApplyContext& ctx) : Transactor(ctx)
{
}
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,26 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class ContractClawback : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ContractClawback(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,32 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class ContractCreate : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ContractCreate(ApplyContext& ctx) : Transactor(ctx)
{
}
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,34 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class ContractDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ContractDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
// Interface used by DeleteAccount
static TER
deleteContract(
ApplyView& view,
std::shared_ptr<SLE> const& sle,
AccountID const& account,
beast::Journal j);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,29 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class ContractModify : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ContractModify(ApplyContext& ctx) : Transactor(ctx)
{
}
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,26 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class ContractUserDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ContractUserDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -13,12 +13,21 @@ public:
{
}
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

@@ -1,8 +1,8 @@
#pragma once
#include <xrpl/ledger/helpers/NFTokenUtils.h>
#include <xrpl/protocol/nft.h>
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
namespace xrpl {

View File

@@ -0,0 +1,72 @@
#pragma once
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/helpers/ContractUtils.h>
#include <xrpl/protocol/st.h>
#include <xrpl/tx/ApplyContext.h>
#include <queue>
namespace xrpl {
struct ParameterValueVec
{
STData const value;
};
struct FunctionParameterValueVecWithName
{
Blob const name;
STData const value;
};
struct ParameterTypeVec
{
STDataType const type;
};
std::vector<ParameterValueVec>
getParameterValueVec(STArray const& functionParameters);
std::vector<ParameterTypeVec>
getParameterTypeVec(STArray const& functionParameters);
enum ExitType : uint8_t {
UNSET = 0,
WASM_ERROR = 1,
ROLLBACK = 2,
ACCEPT = 3,
};
struct ContractResult
{
uint256 const contractHash; // Hash of the contract code
Keylet const contractKeylet; // Keylet for the contract instance
Keylet const contractSourceKeylet; // Keylet for the contract source
Keylet const contractAccountKeylet; // Keylet for the contract account
AccountID const contractAccount; // AccountID of the contract account
std::uint32_t nextSequence; // Next sequence number for the contract account
AccountID const otxnAccount; // AccountID for the originating transaction
uint256 const otxnId; // ID for the originating transaction
std::string exitReason{""};
int64_t exitCode{-1};
ContractDataMap dataMap;
ContractEventMap eventMap;
std::queue<std::shared_ptr<STTx const>> emittedTxns{};
std::size_t changedDataCount{0};
};
struct ContractContext
{
ApplyContext& applyCtx;
std::vector<ParameterValueVec> instanceParameters;
std::vector<ParameterValueVec> functionParameters;
std::vector<STObject> built_txns;
int64_t expected_etxn_count{-1}; // expected emitted transaction count
std::map<uint256, bool> nonce_used{}; // nonces used in this execution
uint32_t generation = 0; // generation of the contract being executed
uint64_t burden = 0; // computational burden used
ContractResult result;
};
} // namespace xrpl

View File

@@ -0,0 +1,95 @@
#pragma once
#include <xrpl/tx/wasm/ContractContext.h>
#include <xrpl/tx/wasm/HostFunc.h>
#include <xrpl/tx/wasm/HostFuncImpl.h>
namespace xrpl {
class ContractHostFunctionsImpl : public WasmHostFunctionsImpl
{
ContractContext& contractCtx;
uint256 const contractId = contractCtx.result.contractKeylet.key;
public:
// Constructor for contract-specific functionality
ContractHostFunctionsImpl(ContractContext& contractContext)
: WasmHostFunctionsImpl(contractContext.applyCtx, contractContext.result.contractKeylet)
, contractCtx(contractContext)
{
}
// Expected<Bytes, HostFunctionError>
// getFieldBytesFromSTData(xrpl::STData const& funcParam, std::uint32_t
// stTypeId);
Expected<Bytes, HostFunctionError>
instanceParam(std::uint32_t index, std::uint32_t stTypeId) override;
Expected<Bytes, HostFunctionError>
functionParam(std::uint32_t index, std::uint32_t stTypeId) override;
Expected<Bytes, HostFunctionError>
getDataObjectField(AccountID const& account, std::string_view const& key) override;
Expected<Bytes, HostFunctionError>
getDataNestedObjectField(
AccountID const& account,
std::string_view const& key,
std::string_view const& nestedKey) override;
Expected<Bytes, HostFunctionError>
getDataArrayElementField(AccountID const& account, size_t index, std::string_view const& key)
override;
Expected<Bytes, HostFunctionError>
getDataNestedArrayElementField(
AccountID const& account,
std::string_view const& key,
size_t index,
std::string_view const& nestedKey) override;
Expected<int32_t, HostFunctionError>
setDataObjectField(
AccountID const& account,
std::string_view const& key,
STJson::Value const& value) override;
Expected<int32_t, HostFunctionError>
setDataNestedObjectField(
AccountID const& account,
std::string_view const& nestedKey,
std::string_view const& key,
STJson::Value const& value) override;
Expected<int32_t, HostFunctionError>
setDataArrayElementField(
AccountID const& account,
size_t index,
std::string_view const& key,
STJson::Value const& value) override;
Expected<int32_t, HostFunctionError>
setDataNestedArrayElementField(
AccountID const& account,
std::string_view const& key,
size_t index,
std::string_view const& nestedKey,
STJson::Value const& value) override;
Expected<int32_t, HostFunctionError>
buildTxn(std::uint16_t const& txType) override;
Expected<int32_t, HostFunctionError>
addTxnField(std::uint32_t const& index, SField const& field, Slice const& data) override;
Expected<int32_t, HostFunctionError>
emitBuiltTxn(std::uint32_t const& index) override;
Expected<int32_t, HostFunctionError>
emitTxn(std::shared_ptr<STTx const> const& stxPtr) override;
Expected<int32_t, HostFunctionError>
emitEvent(std::string_view const& eventName, STJson const& eventData) override;
};
} // namespace xrpl

View File

@@ -6,6 +6,8 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/STJson.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/tx/wasm/ParamsHelper.h>
@@ -13,6 +15,7 @@
namespace xrpl {
enum class HostFunctionError : int32_t {
SUCCESS = 0,
INTERNAL = -1,
FIELD_NOT_FOUND = -2,
BUFFER_TOO_SMALL = -3,
@@ -35,6 +38,8 @@ enum class HostFunctionError : int32_t {
FLOAT_COMPUTATION_ERROR = -20,
NO_RUNTIME = -21,
OUT_OF_GAS = -22,
SUBMIT_TXN_FAILURE = -23,
INVALID_STATE = -24,
};
inline int32_t
@@ -494,6 +499,119 @@ struct HostFunctions
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<Bytes, HostFunctionError>
instanceParam(std::uint32_t index, std::uint32_t stTypeId)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<Bytes, HostFunctionError>
functionParam(std::uint32_t index, std::uint32_t stTypeId)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<Bytes, HostFunctionError>
getDataObjectField(AccountID const& account, std::string_view const& key)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<Bytes, HostFunctionError>
getDataNestedObjectField(
AccountID const& account,
std::string_view const& key,
std::string_view const& nestedKey)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<Bytes, HostFunctionError>
getDataArrayElementField(AccountID const& account, size_t index, std::string_view const& key)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<Bytes, HostFunctionError>
getDataNestedArrayElementField(
AccountID const& account,
std::string_view const& key,
size_t index,
std::string_view const& nestedKey)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<int32_t, HostFunctionError>
setDataObjectField(
AccountID const& account,
std::string_view const& keyName,
STJson::Value const& value)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<int32_t, HostFunctionError>
setDataNestedObjectField(
AccountID const& account,
std::string_view const& nestedKey,
std::string_view const& key,
STJson::Value const& value)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<int32_t, HostFunctionError>
setDataArrayElementField(
AccountID const& account,
size_t index,
std::string_view const& key,
STJson::Value const& value)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<int32_t, HostFunctionError>
setDataNestedArrayElementField(
AccountID const& account,
std::string_view const& key,
size_t index,
std::string_view const& nestedKey,
STJson::Value const& value)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<int32_t, HostFunctionError>
buildTxn(std::uint16_t const& txType)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<int32_t, HostFunctionError>
addTxnField(std::uint32_t const& index, SField const& field, Slice const& data)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<int32_t, HostFunctionError>
emitBuiltTxn(std::uint32_t const& index)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<int32_t, HostFunctionError>
emitTxn(std::shared_ptr<STTx const> const& stxPtr)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual Expected<int32_t, HostFunctionError>
emitEvent(std::string_view const& eventName, STJson const& eventData)
{
return Unexpected(HostFunctionError::INTERNAL);
}
virtual ~HostFunctions() = default;
// LCOV_EXCL_STOP
};

View File

@@ -300,4 +300,96 @@ using floatLog_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32
wasm_trap_t*
floatLog_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
// Contract-specific host function wrappers
using instanceParam_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t);
wasm_trap_t*
instanceParam_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using functionParam_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t);
wasm_trap_t*
functionParam_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using getDataObjectField_proto =
int32_t(uint8_t*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t*
getDataObjectField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using getDataNestedObjectField_proto =
int32_t(uint8_t*, int32_t, uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t*
getDataNestedObjectField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using setDataObjectField_proto =
int32_t(uint8_t*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t*
setDataObjectField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using setDataNestedObjectField_proto =
int32_t(uint8_t*, int32_t, uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t*
setDataNestedObjectField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using getDataArrayElementField_proto =
int32_t(uint8_t*, int32_t, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t*
getDataArrayElementField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using getDataNestedArrayElementField_proto = int32_t(
uint8_t*,
int32_t,
uint8_t const*,
int32_t,
int32_t,
uint8_t const*,
int32_t,
uint8_t*,
int32_t);
wasm_trap_t*
getDataNestedArrayElementField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using setDataArrayElementField_proto =
int32_t(uint8_t*, int32_t, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t*
setDataArrayElementField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using setDataNestedArrayElementField_proto = int32_t(
uint8_t*,
int32_t,
uint8_t const*,
int32_t,
int32_t,
uint8_t const*,
int32_t,
uint8_t*,
int32_t);
wasm_trap_t*
setDataNestedArrayElementField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using buildTxn_proto = int32_t(int32_t);
wasm_trap_t*
buildTxn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using addTxnField_proto = int32_t(int32_t, int32_t, uint8_t const*, int32_t);
wasm_trap_t*
addTxnField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using emitBuiltTxn_proto = int32_t(int32_t);
wasm_trap_t*
emitBuiltTxn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using emitTxn_proto = int32_t(uint8_t const*, int32_t);
wasm_trap_t*
emitTxn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using emitEvent_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
wasm_trap_t*
emitEvent_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
} // namespace xrpl

View File

@@ -31,6 +31,7 @@ struct WasmResult
int64_t cost;
};
typedef WasmResult<int32_t> EscrowResult;
typedef WasmResult<int32_t> WasmRunResult;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -181,6 +182,43 @@ wasmParamsHlp(std::vector<WasmParam>& v, std::int64_t p, Types&&... args)
// wasmParamsHlp(v, std::forward<Types>(args)...);
// }
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, std::uint8_t const* dt, std::int32_t sz, Types&&... args)
{
v.push_back({.type = WT_U8V, .of = {.u8v = {.d = dt, .sz = sz}}});
wasmParamsHlp(v, std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, Bytes const& p, Types&&... args)
{
wasmParamsHlp(v, p.data(), static_cast<std::int32_t>(p.size()), std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, std::string_view const& p, Types&&... args)
{
wasmParamsHlp(
v,
reinterpret_cast<std::uint8_t const*>(p.data()),
static_cast<std::int32_t>(p.size()),
std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, std::string const& p, Types&&... args)
{
wasmParamsHlp(
v,
reinterpret_cast<std::uint8_t const*>(p.c_str()),
static_cast<std::int32_t>(p.size()),
std::forward<Types>(args)...);
}
inline void
wasmParamsHlp(std::vector<WasmParam>& v)
{

View File

@@ -89,6 +89,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& wasmReturnCode,
bool isDryRun,
beast::Journal j)
{
@@ -103,6 +105,8 @@ ApplyStateTable::apply(
meta.setDeliveredAmount(deliver);
meta.setParentBatchID(parentBatchId);
meta.setGasUsed(gasUsed);
meta.setWasmReturnCode(wasmReturnCode);
Mods newMod;
for (auto& item : items_)

View File

@@ -15,7 +15,8 @@ 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_, wasmReturnCode_, isDryRun, j);
}
std::size_t

View File

@@ -443,6 +443,271 @@ doWithdraw(
return accountSend(view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes);
}
static TER
canTransferIOU(
ReadView const& view,
AccountID const& sender,
AccountID const& receiver,
STAmount const& amount,
beast::Journal j,
SendIssuerHandling issuerHandling,
SendEscrowHandling escrowHandling,
SendAuthHandling authHandling,
SendFreezeHandling freezeHandling,
SendTransferHandling transferHandling,
SendBalanceHandling balanceHandling)
{
AccountID issuer = amount.getIssuer();
// If the issuer is the same as the sender
if (issuerHandling == SendIssuerHandling::ihSENDER_NOT_ALLOWED && issuer == sender)
return tecNO_PERMISSION;
// If the issuer is the same as the receiver
if (issuerHandling == SendIssuerHandling::ihRECEIVER_NOT_ALLOWED && issuer == receiver)
return tecNO_PERMISSION;
// If the lsfAllowTrustLineLocking is not enabled
auto const sleIssuer = view.read(keylet::account(issuer));
if (!sleIssuer)
return tecNO_ISSUER;
if (issuerHandling != SendIssuerHandling::ihSENDER_NOT_ALLOWED &&
issuerHandling != SendIssuerHandling::ihRECEIVER_NOT_ALLOWED &&
!sleIssuer->isFlag(lsfDefaultRipple))
return terNO_RIPPLE;
if (escrowHandling == SendEscrowHandling::ehCHECK &&
!sleIssuer->isFlag(lsfAllowTrustLineLocking))
return tecNO_PERMISSION;
// If the sender does not have a trustline to the issuer
auto const sleRippleState = view.read(keylet::line(sender, issuer, amount.getCurrency()));
if (!sleRippleState)
return tecNO_LINE;
STAmount const balance = (*sleRippleState)[sfBalance];
// If balance is positive, issuer must have higher address than sender
if (balance > beast::zero && issuer < sender)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If balance is negative, issuer must have lower address than sender
if (balance < beast::zero && issuer > sender)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// // If the account trustline has no-ripple set for the issuer
// if (auto const ter = requireNoRipple(ctx.view, amount.issue(), account);
// ter != tesSUCCESS)
// return ter;
// // If the dest trustline has no-ripple set for the issuer
// if (auto const ter = requireNoRipple(ctx.view, amount.issue(), dest);
// ter != tesSUCCESS)
// return ter;
// If the issuer has requireAuth set, check if the sender is authorized
if (authHandling == SendAuthHandling::ahCHECK_SENDER ||
authHandling == SendAuthHandling::ahBOTH)
{
if (auto const ter = requireAuth(view, amount.issue(), sender); ter != tesSUCCESS)
return ter;
}
// If the issuer has requireAuth set, check if the receiver is authorized
if (authHandling == SendAuthHandling::ahCHECK_RECEIVER ||
authHandling == SendAuthHandling::ahBOTH)
{
if (auto const ter = requireAuth(view, amount.issue(), receiver); ter != tesSUCCESS)
return ter;
}
// If the issuer has frozen the sender
if ((freezeHandling == SendFreezeHandling::fhCHECK_SENDER ||
freezeHandling == SendFreezeHandling::fhBOTH) &&
isFrozen(view, sender, amount.issue()))
return tecFROZEN;
// If the issuer has frozen the receiver
if ((freezeHandling == SendFreezeHandling::fhCHECK_RECEIVER ||
freezeHandling == SendFreezeHandling::fhBOTH) &&
isFrozen(view, receiver, amount.issue()))
return tecFROZEN;
if (balanceHandling == SendBalanceHandling::bhIGNORE)
return tesSUCCESS;
STAmount const spendableAmount = accountHolds(
view,
sender,
amount.get<Issue>(),
fhIGNORE_FREEZE, // already checked freeze above
ahIGNORE_AUTH, // already checked auth above
j);
// If the balance is less than or equal to 0
if (spendableAmount <= beast::zero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
// If the amount is not addable to the balance
if (!canAdd(spendableAmount, amount))
return tecPRECISION_LOSS;
return tesSUCCESS;
}
static TER
canTransferMPT(
ReadView const& view,
AccountID const& sender,
AccountID const& receiver,
STAmount const& amount,
beast::Journal j,
SendIssuerHandling issuerHandling,
SendEscrowHandling escrowHandling,
SendAuthHandling authHandling,
SendFreezeHandling freezeHandling,
SendTransferHandling transferHandling,
SendBalanceHandling balanceHandling)
{
AccountID issuer = amount.getIssuer();
// If the issuer is the same as the sender
if (issuerHandling == SendIssuerHandling::ihSENDER_NOT_ALLOWED && issuer == sender)
return tecNO_PERMISSION;
// If the issuer is the same as the receiver
if (issuerHandling == SendIssuerHandling::ihRECEIVER_NOT_ALLOWED && issuer == receiver)
return tecNO_PERMISSION;
// If the mpt does not exist
auto const issuanceKey = keylet::mptIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the lsfMPTCanEscrow is not enabled
if (escrowHandling == SendEscrowHandling::ehCHECK && !sleIssuance->isFlag(lsfMPTCanEscrow))
return tecNO_PERMISSION;
// If the issuer is not the same as the issuer of the mpt
if (sleIssuance->getAccountID(sfIssuer) != issuer)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the sender does not have the mpt
if (!view.exists(keylet::mptoken(issuanceKey.key, sender)))
return tecOBJECT_NOT_FOUND;
auto const& mptIssue = amount.get<MPTIssue>();
// If the issuer has requireAuth set, check if the sender is authorized
if (authHandling == SendAuthHandling::ahCHECK_SENDER ||
authHandling == SendAuthHandling::ahBOTH)
{
if (auto const ter = requireAuth(view, mptIssue, sender, AuthType::WeakAuth);
ter != tesSUCCESS)
return ter;
}
// If the issuer has requireAuth set, check if the receiver is authorized
if (authHandling == SendAuthHandling::ahCHECK_RECEIVER ||
authHandling == SendAuthHandling::ahBOTH)
{
if (auto const ter = requireAuth(view, mptIssue, receiver, AuthType::WeakAuth);
ter != tesSUCCESS)
return ter;
}
// If the issuer has frozen the sender, return tecLOCKED
if ((freezeHandling == SendFreezeHandling::fhCHECK_SENDER ||
freezeHandling == SendFreezeHandling::fhBOTH) &&
isFrozen(view, sender, mptIssue))
return tecLOCKED;
// If the issuer has frozen the receiver, return tecLOCKED
if ((freezeHandling == SendFreezeHandling::fhCHECK_RECEIVER ||
freezeHandling == SendFreezeHandling::fhBOTH) &&
isFrozen(view, receiver, mptIssue))
return tecLOCKED;
// If the mpt cannot be transferred, return tecNO_AUTH
if (transferHandling == SendTransferHandling::thCHECK)
{
if (auto const ter = canTransfer(view, mptIssue, sender, receiver); ter != tesSUCCESS)
return ter;
}
if (balanceHandling == SendBalanceHandling::bhIGNORE)
return tesSUCCESS;
STAmount const spendableAmount = accountHolds(
view,
sender,
amount.get<MPTIssue>(),
fhIGNORE_FREEZE, // already checked freeze above
ahIGNORE_AUTH, // already checked auth above
j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::zero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
return tesSUCCESS;
}
TER
canTransferFT(
ReadView const& view,
AccountID const& sender,
AccountID const& receiver,
STAmount const& amount,
beast::Journal j,
SendIssuerHandling issuerHandling = SendIssuerHandling::ihIGNORE,
SendEscrowHandling escrowHandling = SendEscrowHandling::ehIGNORE,
SendAuthHandling authHandling = SendAuthHandling::ahBOTH,
SendFreezeHandling freezeHandling = SendFreezeHandling::fhBOTH,
SendTransferHandling transferHandling = SendTransferHandling::thIGNORE,
SendBalanceHandling balanceHandling = SendBalanceHandling::bhCHECK)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
if constexpr (std::is_same_v<TIss, Issue>)
return canTransferIOU(
view,
sender,
receiver,
amount,
j,
issuerHandling,
escrowHandling,
authHandling,
freezeHandling,
transferHandling,
balanceHandling);
else
return canTransferMPT(
view,
sender,
receiver,
amount,
j,
issuerHandling,
escrowHandling,
authHandling,
freezeHandling,
transferHandling,
balanceHandling);
},
amount.asset().value());
}
TER
cleanupOnAccountDelete(
ApplyView& view,

View File

@@ -0,0 +1,643 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/ContractUtils.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/NFTokenUtils.h>
#include <xrpl/protocol/st.h>
#include <xrpl/server/NetworkOPs.h>
namespace xrpl {
namespace contract {
struct BlobHash
{
std::size_t
operator()(Blob const& b) const noexcept
{
if (b.empty())
return 0;
return std::hash<std::string_view>{}(
std::string_view(reinterpret_cast<char const*>(b.data()), b.size()));
}
};
int64_t
contractCreateFee(uint64_t byteCount)
{
constexpr uint64_t mul = static_cast<uint64_t>(createByteMultiplier);
if (byteCount > std::numeric_limits<uint64_t>::max() / mul)
return feeCalculationFailed; // overflow
uint64_t uf = byteCount * mul;
if (uf > static_cast<uint64_t>(std::numeric_limits<int64_t>::max()))
return feeCalculationFailed;
return static_cast<int64_t>(uf);
}
NotTEC
preflightFunctions(STTx const& tx, beast::Journal j)
{
// Functions must be present if ContractCode is present.
if (!tx.isFieldPresent(sfContractCode))
return tesSUCCESS;
if (!tx.isFieldPresent(sfFunctions))
{
JLOG(j.trace()) << "ContractCreate/Modify: ContractCode present but "
"Functions missing.";
return temARRAY_EMPTY;
}
auto const& functions = tx.getFieldArray(sfFunctions);
if (functions.empty())
{
JLOG(j.trace()) << "ContractCreate/Modify: Functions array empty.";
return temARRAY_EMPTY;
}
// Functions must not exceed n entries.
if (functions.size() > contract::maxContractFunctions)
{
JLOG(j.trace()) << "ContractCreate/Modify: Functions array too large.";
return temARRAY_TOO_LARGE;
}
std::unordered_set<Blob, BlobHash> uniqueFunctions;
uniqueFunctions.reserve(functions.size());
for (auto const& function : functions)
{
// Functions must be unique by name.
auto const& functionName = function.getFieldVL(sfFunctionName);
if (!uniqueFunctions.insert(functionName).second)
{
JLOG(j.trace()) << "Duplicate function name: " << strHex(functionName);
return temREDUNDANT;
}
auto const& parameters = function.getFieldArray(sfParameters);
// Function Parameters must not exceed n entries each.
if (parameters.size() > contract::maxContractParams)
{
JLOG(j.trace()) << "ContractCreate/Modify: Function Parameters "
"array is too large.";
return temARRAY_TOO_LARGE;
}
std::unordered_set<Blob, BlobHash> uniqueParameters;
uniqueParameters.reserve(parameters.size());
for (auto const& param : parameters)
{
// Function Parameter must have a flag.
if (!param.isFieldPresent(sfParameterFlag))
{
JLOG(j.trace()) << "ContractCreate/Modify: Function Parameter "
"is missing flag.";
return temMALFORMED;
}
// Function Parameter must have a type.
if (!param.isFieldPresent(sfParameterType))
{
JLOG(j.trace()) << "ContractCreate/Modify: Function Parameter "
"is missing type.";
return temMALFORMED;
}
// Function Parameter flags must be valid.
auto const flags = param.getFieldU32(sfParameterFlag);
if (flags & tfContractParameterMask)
{
JLOG(j.trace()) << "ContractCreate/Modify: Invalid parameter "
"flag in Function.";
return temINVALID_FLAG;
}
}
}
return tesSUCCESS;
}
NotTEC
preflightInstanceParameters(STTx const& tx, beast::Journal j)
{
if (!tx.isFieldPresent(sfInstanceParameters))
return tesSUCCESS;
auto const& instanceParameters = tx.getFieldArray(sfInstanceParameters);
// InstanceParameters must not be empty.
if (instanceParameters.empty())
{
JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameters empty array.";
return temARRAY_EMPTY;
}
// InstanceParameters must not exceed n entries.
if (instanceParameters.size() > contract::maxContractParams)
{
JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameters array "
"is too large.";
return temARRAY_TOO_LARGE;
}
std::unordered_set<Blob, BlobHash> uniqueParameters;
uniqueParameters.reserve(instanceParameters.size());
for (auto const& param : instanceParameters)
{
// Instance Parameter must have a flag.
if (!param.isFieldPresent(sfParameterFlag))
{
JLOG(j.trace()) << "ContractCreate/Modify: Instance Parameter is missing flag.";
return temMALFORMED;
}
// Instance Parameter must have a type.
if (!param.isFieldPresent(sfParameterType))
{
JLOG(j.trace()) << "ContractCreate/Modify: Instance Parameter is missing type.";
return temMALFORMED;
}
// Instance Parameter flags must be valid.
auto const flags = param.getFieldU32(sfParameterFlag);
if (flags & tfContractParameterMask)
{
JLOG(j.trace()) << "ContractCreate/Modify: Invalid parameter "
"flag in Instance Parameter.";
return temINVALID_FLAG;
}
}
return tesSUCCESS;
}
bool
validateParameterMapping(STArray const& params, STArray const& values, beast::Journal j)
{
if (params.size() != values.size())
{
JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameterValues size "
"does not match InstanceParameters size.";
return false;
}
return true;
}
NotTEC
preflightInstanceParameterValues(STTx const& tx, beast::Journal j)
{
if (!tx.isFieldPresent(sfInstanceParameterValues))
return tesSUCCESS;
auto const& instanceParameterValues = tx.getFieldArray(sfInstanceParameterValues);
// InstanceParameters must not be empty.
if (instanceParameterValues.empty())
{
JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameterValues is missing.";
return temARRAY_EMPTY;
}
// InstanceParameterValues must not exceed n entries.
if (instanceParameterValues.size() > contract::maxContractParams)
{
JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameterValues "
"array is too large.";
return temARRAY_TOO_LARGE;
}
std::unordered_set<Blob, BlobHash> uniqueParameters;
uniqueParameters.reserve(instanceParameterValues.size());
for (auto const& param : instanceParameterValues)
{
// Instance Parameter must have a flag.
if (!param.isFieldPresent(sfParameterFlag))
{
JLOG(j.trace()) << "ContractCreate/Modify: Instance Parameter is missing flag.";
return temMALFORMED;
}
// Instance Parameter must have a value.
if (!param.isFieldPresent(sfParameterValue))
{
JLOG(j.trace()) << "ContractCreate/Modify: Instance Parameter is "
"missing value.";
return temMALFORMED;
}
// Instance Parameter flags must be valid.
auto const flags = param.getFieldU32(sfParameterFlag);
if (flags & tfContractParameterMask)
{
JLOG(j.trace()) << "ContractCreate/Modify: Invalid parameter "
"flag in Instance Parameter.";
return temINVALID_FLAG;
}
}
// Only validate the mapping if InstanceParameters are present
bool valid = true;
if (tx.isFieldPresent(sfInstanceParameters))
valid = validateParameterMapping(
tx.getFieldArray(sfInstanceParameters), tx.getFieldArray(sfInstanceParameterValues), j);
if (!valid)
{
JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameterValues do not match "
"InstanceParameters.";
return temMALFORMED;
}
// Validate flags in InstanceParameterValues
if (auto const res = preflightFlagParameters(instanceParameterValues, j); !isTesSuccess(res))
{
JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameterValues flag "
"validation failed: "
<< transToken(res);
return res;
}
return tesSUCCESS;
}
bool
isValidParameterFlag(std::uint32_t flags)
{
return (flags & tfContractParameterMask) == 0;
}
NotTEC
preflightFlagParameters(STArray const& parameters, beast::Journal j)
{
for (auto const& param : parameters)
{
if (!param.isFieldPresent(sfParameterFlag) ||
!isValidParameterFlag(param.getFieldU32(sfParameterFlag)))
continue; // Skip invalid flags
switch (param.getFieldU32(sfParameterFlag))
{
case tfSendAmount: {
if (!param.isFieldPresent(sfParameterValue))
return temMALFORMED;
auto const& value = param.getFieldData(sfParameterValue);
STAmount amount = value.getFieldAmount();
// Preflight Transfer Amount
if (isXRP(amount))
{
if (amount <= beast::zero)
return temBAD_AMOUNT;
}
else if (amount.holds<Issue>())
{
if (amount.native() || amount <= beast::zero)
return temBAD_AMOUNT;
if (badCurrency() == amount.getCurrency())
return temBAD_CURRENCY;
}
else if (amount.holds<MPTIssue>())
{
if (amount.native() || amount.mpt() > MPTAmount{maxMPTokenAmount} ||
amount <= beast::zero)
return temBAD_AMOUNT;
}
break;
}
case tfSendNFToken: {
break;
}
case tfAuthorizeToken: {
return temDISABLED;
break;
}
}
}
return tesSUCCESS;
}
TER
preclaimFlagParameters(
ReadView const& view,
AccountID const& sourceAccount,
AccountID const& contractAccount,
STArray const& parameters,
beast::Journal j)
{
for (auto const& param : parameters)
{
if (!param.isFieldPresent(sfParameterFlag) ||
!isValidParameterFlag(param.getFieldU32(sfParameterFlag)))
continue; // Skip invalid flags
switch (param.getFieldU32(sfParameterFlag))
{
case tfSendAmount: {
if (!param.isFieldPresent(sfParameterValue))
return tecINTERNAL;
auto const& value = param.getFieldData(sfParameterValue);
STAmount amount = value.getFieldAmount();
// Preclaim Transfer Amount
if (isXRP(amount))
{
auto const accountSle = view.read(keylet::account(sourceAccount));
if (!accountSle)
return tecINTERNAL;
auto const& mSourceBalance = accountSle->getFieldAmount(sfBalance);
if (mSourceBalance < amount.xrp())
return tecUNFUNDED;
}
else
{
if (auto ter = canTransferFT(
view,
sourceAccount,
contractAccount,
amount,
j,
SendIssuerHandling::ihIGNORE,
SendEscrowHandling::ehIGNORE,
SendAuthHandling::ahBOTH,
SendFreezeHandling::fhBOTH,
SendTransferHandling::thIGNORE,
SendBalanceHandling::bhCHECK))
{
JLOG(j.trace()) << "preclaimFlagParameters: Cannot "
"transfer amount: "
<< amount;
return ter;
}
}
break;
}
case tfSendNFToken: {
if (!param.isFieldPresent(sfParameterValue))
return tecINTERNAL;
auto const& value = param.getFieldData(sfParameterValue);
auto const& nftokenID = value.getFieldH256();
// Preclaim Transfer NFT Token
if (!nft::findToken(view, sourceAccount, nftokenID))
{
JLOG(j.trace())
<< "preclaimFlagParameters: Cannot transfer NFT token: " << nftokenID;
return tecNO_ENTRY;
}
break;
}
case tfAuthorizeToken: {
break;
}
}
}
return tesSUCCESS;
}
TER
doApplyFlagParameters(
ApplyView& view,
STTx const& tx,
AccountID const& sourceAccount,
AccountID const& contractAccount,
STArray const& parameters,
XRPAmount const& priorBalance,
beast::Journal j)
{
for (auto const& param : parameters)
{
if (!param.isFieldPresent(sfParameterFlag) ||
!isValidParameterFlag(param.getFieldU32(sfParameterFlag)))
continue; // Skip invalid flags
switch (param.getFieldU32(sfParameterFlag))
{
case tfSendAmount: {
if (!param.isFieldPresent(sfParameterValue))
return tecINTERNAL;
auto const& value = param.getFieldData(sfParameterValue);
STAmount amount = value.getFieldAmount();
if (auto ter = accountSend(
view, sourceAccount, contractAccount, amount, j, WaiveTransferFee::No);
!isTesSuccess(ter))
{
JLOG(j.trace()) << "doApplyFlagParameters: Failed to send amount: " << amount;
return ter;
}
break;
}
case tfSendNFToken: {
if (!param.isFieldPresent(sfParameterValue))
return tecINTERNAL;
auto const& value = param.getFieldData(sfParameterValue);
auto const& nftokenID = value.getFieldH256();
if (auto ter =
nft::transferNFToken(view, sourceAccount, contractAccount, nftokenID);
!isTesSuccess(ter))
{
JLOG(j.trace())
<< "doApplyFlagParameters: Failed to send NFT token: " << nftokenID;
return ter;
}
break;
}
case tfAuthorizeToken: {
return tecINTERNAL;
// if (!param.isFieldPresent(sfParameterValue))
// return tecINTERNAL;
// // Handle tfAuthorizeToken if needed
// auto const& value = param.getFieldData(sfParameterValue);
// STAmount limit = value.getFieldAmount();
// Asset asset = Asset{limit.issue()};
// if (auto ter = canAddHolding(view, asset);
// !isTesSuccess(ter))
// {
// JLOG(j.trace()) << "doApplyFlagParameters: Cannot add "
// "holding for asset: "
// << to_string(asset);
// return ter;
// }
// // Set the issuer to the contract account for the holding
// limit.setIssuer(contractAccount);
// if (auto ter = addEmptyHolding(
// view, contractAccount, priorBalance, asset, limit,
// j);
// !isTesSuccess(ter))
// {
// JLOG(j.trace()) << "doApplyFlagParameters: Failed to add
// "
// "holding for asset: "
// << to_string(asset);
// return ter;
// }
// break;
}
}
}
return tesSUCCESS;
}
uint32_t
contractDataReserve(uint32_t size)
{
// Divide by dataByteMultiplier and round up to the nearest whole number
return (size + dataByteMultiplier - 1U) / dataByteMultiplier;
}
TER
setContractData(
ServiceRegistry& registry,
ApplyView& view,
AccountID const& account,
AccountID const& contractAccount,
STJson const& data)
{
auto const j = registry.getJournal("View");
auto const sleAccount = view.peek(keylet::account(account));
if (!sleAccount)
return tefINTERNAL;
// if the blob is too large don't set it
if (data.size() > maxContractDataSize)
return temARRAY_TOO_LARGE;
auto dataKeylet = keylet::contractData(account, contractAccount);
auto dataSle = view.peek(dataKeylet);
// DELETE
if (data.size() == 0)
{
if (!dataSle)
return tesSUCCESS;
uint32_t oldDataReserve = contractDataReserve(dataSle->getFieldJson(sfContractJson).size());
std::uint64_t const page = (*dataSle)[sfOwnerNode];
// Remove the page from the account directory
if (!view.dirRemove(keylet::ownerDir(account), page, dataKeylet.key, false))
return tefBAD_LEDGER;
// remove the actual contract data sle
view.erase(dataSle);
// reduce the owner count
adjustOwnerCount(view, sleAccount, -oldDataReserve, j);
return tesSUCCESS;
}
std::uint32_t ownerCount{(*sleAccount)[sfOwnerCount]};
bool createNew = !dataSle;
if (createNew)
{
// CREATE
uint32_t dataReserve = contractDataReserve(data.size());
uint32_t newReserve = ownerCount + dataReserve;
XRPAmount const newReserveAmount{view.fees().accountReserve(newReserve)};
if (STAmount((*sleAccount)[sfBalance]).xrp() < newReserveAmount)
return tecINSUFFICIENT_RESERVE;
adjustOwnerCount(view, sleAccount, dataReserve, j);
// create an entry
dataSle = std::make_shared<SLE>(dataKeylet);
dataSle->setFieldJson(sfContractJson, data);
dataSle->setAccountID(sfOwner, account);
dataSle->setAccountID(sfContractAccount, contractAccount);
auto const page =
view.dirInsert(keylet::ownerDir(account), dataKeylet.key, describeOwnerDir(account));
if (!page)
return tecDIR_FULL;
dataSle->setFieldU64(sfOwnerNode, *page);
// add new data to ledger
view.insert(dataSle);
}
else
{
// UPDATE
uint32_t oldDataReserve = contractDataReserve(dataSle->getFieldJson(sfContractJson).size());
uint32_t newDataReserve = contractDataReserve(data.size());
if (newDataReserve != oldDataReserve)
{
// if the reserve changes, we need to adjust the owner count
uint32_t newReserve = ownerCount - oldDataReserve + newDataReserve;
XRPAmount const newReserveAmount{view.fees().accountReserve(newReserve)};
if (STAmount((*sleAccount)[sfBalance]).xrp() < newReserveAmount)
return tecINSUFFICIENT_RESERVE;
adjustOwnerCount(view, sleAccount, newReserve, j);
}
// update the data
dataSle->setFieldJson(sfContractJson, data);
view.update(dataSle);
}
return tesSUCCESS;
}
TER
finalizeContractData(
ServiceRegistry& registry,
ApplyView& view,
AccountID const& contractAccount,
ContractDataMap const& dataMap,
ContractEventMap const& eventMap,
uint256 const& txnID)
{
auto const& j = registry.getJournal("View");
uint16_t changeCount = 0;
for (auto const& [name, data] : eventMap)
registry.getOPs().pubContractEvent(name, data);
for (auto const& accEntry : dataMap)
{
auto const& acc = accEntry.first;
auto const& cacheEntry = accEntry.second;
bool is_modified = cacheEntry.first;
auto const& jsonData = cacheEntry.second;
if (is_modified)
{
changeCount++;
if (changeCount > maxDataModifications)
{
// overflow
JLOG(j.trace()) << "ContractError[TX:" << txnID
<< "]: SetContractData failed: Too many data changes";
return tecWASM_REJECTED;
}
TER result = setContractData(registry, view, acc, contractAccount, jsonData);
if (!isTesSuccess(result))
{
JLOG(j.warn()) << "ContractError[TX:" << txnID
<< "]: SetContractData failed: " << result << " Account: " << acc;
return result;
}
}
}
return tesSUCCESS;
}
} // namespace contract
} // namespace xrpl

View File

@@ -1,15 +1,16 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/algorithm.h>
#include <xrpl/ledger/Dir.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/NFTokenUtils.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/nftPageMask.h>
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
#include <functional>
#include <memory>
@@ -1106,5 +1107,56 @@ checkTrustlineDeepFrozen(
return tesSUCCESS;
}
TER
transferNFToken(
ApplyView& view,
AccountID const& buyer,
AccountID const& seller,
uint256 const& nftokenID)
{
auto tokenAndPage = nft::findTokenAndPage(view, seller, nftokenID);
if (!tokenAndPage)
return tecINTERNAL; // LCOV_EXCL_LINE
if (auto const ret = nft::removeToken(view, seller, nftokenID, std::move(tokenAndPage->page));
!isTesSuccess(ret))
return ret;
auto const sleBuyer = view.read(keylet::account(buyer));
if (!sleBuyer)
return tecINTERNAL; // LCOV_EXCL_LINE
std::uint32_t const buyerOwnerCountBefore = sleBuyer->getFieldU32(sfOwnerCount);
auto const insertRet = nft::insertToken(view, buyer, std::move(tokenAndPage->token));
// if fixNFTokenReserve is enabled, check if the buyer has sufficient
// reserve to own a new object, if their OwnerCount changed.
//
// There was an issue where the buyer accepts a sell offer, the ledger
// didn't check if the buyer has enough reserve, meaning that buyer can get
// NFTs free of reserve.
if (view.rules().enabled(fixNFTokenReserve))
{
// To check if there is sufficient reserve, we cannot use mPriorBalance
// because NFT is sold for a price. So we must use the balance after
// the deduction of the potential offer price. A small caveat here is
// that the balance has already deducted the transaction fee, meaning
// that the reserve requirement is a few drops higher.
auto const buyerBalance = sleBuyer->getFieldAmount(sfBalance);
auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount);
if (buyerOwnerCountAfter > buyerOwnerCountBefore)
{
if (auto const reserve = view.fees().accountReserve(buyerOwnerCountAfter);
buyerBalance < reserve)
return tecINSUFFICIENT_RESERVE;
}
}
return insertRet;
}
} // namespace nft
} // namespace xrpl

View File

@@ -0,0 +1,167 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Emitable.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/jss.h>
namespace xrpl {
Emitable::Emitable()
{
emitableTx_ = {
#pragma push_macro("TRANSACTION")
#undef TRANSACTION
#define TRANSACTION(tag, value, name, delegatable, amendment, permissions, emitable, fields) \
{value, emitable},
#include <xrpl/protocol/detail/transactions.macro>
#undef TRANSACTION
#pragma pop_macro("TRANSACTION")
};
granularEmitableMap_ = {
#pragma push_macro("EMITABLE")
#undef EMITABLE
#define EMITABLE(type, txType, value) {#type, type},
#include <xrpl/protocol/detail/emitable.macro>
#undef EMITABLE
#pragma pop_macro("EMITABLE")
};
granularNameMap_ = {
#pragma push_macro("EMITABLE")
#undef EMITABLE
#define EMITABLE(type, txType, value) {type, #type},
#include <xrpl/protocol/detail/emitable.macro>
#undef EMITABLE
#pragma pop_macro("EMITABLE")
};
granularTxTypeMap_ = {
#pragma push_macro("EMITABLE")
#undef EMITABLE
#define EMITABLE(type, txType, value) {type, txType},
#include <xrpl/protocol/detail/emitable.macro>
#undef EMITABLE
#pragma pop_macro("EMITABLE")
};
for ([[maybe_unused]] auto const& emitable : granularEmitableMap_)
XRPL_ASSERT(
emitable.second > UINT16_MAX,
"xrpl::Emitable::granularEmitableMap_ : granular emitable "
"value must not exceed the maximum uint16_t value.");
}
Emitable const&
Emitable::getInstance()
{
static Emitable const instance;
return instance;
}
std::optional<std::string>
Emitable::getEmitableName(std::uint32_t const value) const
{
auto const emitableValue = static_cast<GranularEmitableType>(value);
if (auto const granular = getGranularName(emitableValue))
return *granular;
// not a granular emitable, check if it maps to a transaction type
auto const txType = emitableToTxType(value);
if (auto const* item = TxFormats::getInstance().findByType(txType); item != nullptr)
return item->getName();
return std::nullopt;
}
std::optional<std::uint32_t>
Emitable::getGranularValue(std::string const& name) const
{
auto const it = granularEmitableMap_.find(name);
if (it != granularEmitableMap_.end())
return static_cast<uint32_t>(it->second);
return std::nullopt;
}
std::optional<std::string>
Emitable::getGranularName(GranularEmitableType const& value) const
{
auto const it = granularNameMap_.find(value);
if (it != granularNameMap_.end())
return it->second;
return std::nullopt;
}
std::optional<TxType>
Emitable::getGranularTxType(GranularEmitableType const& gpType) const
{
auto const it = granularTxTypeMap_.find(gpType);
if (it != granularTxTypeMap_.end())
return it->second;
return std::nullopt;
}
bool
Emitable::isEmitable(std::uint32_t const& emitableValue) const
{
auto const granularEmitable = getGranularName(static_cast<GranularEmitableType>(emitableValue));
if (granularEmitable)
// granular emitables are always allowed to be delegated
return true;
auto const txType = emitableToTxType(emitableValue);
auto const it = emitableTx_.find(txType);
// if (rules.enabled(fixDelegateV1_1))
// {
// if (it == delegatableTx_.end())
// return false;
// auto const txFeaturesIt = txFeatureMap_.find(txType);
// XRPL_ASSERT(
// txFeaturesIt != txFeatureMap_.end(),
// "xrpl::Emitables::isDelegatable : tx exists in txFeatureMap_");
// // fixDelegateV1_1: Delegation is only allowed if the required
// amendment
// // for the transaction is enabled. For transactions that do not
// require
// // an amendment, delegation is always allowed.
// if (txFeaturesIt->second != uint256{} &&
// !rules.enabled(txFeaturesIt->second))
// return false;
// }
if (it != emitableTx_.end() && it->second == Emittance::notEmitable)
return false;
return true;
}
uint32_t
Emitable::txToEmitableType(TxType const& type) const
{
return static_cast<uint32_t>(type) + 1;
}
TxType
Emitable::emitableToTxType(uint32_t const& value) const
{
return static_cast<TxType>(value - 1);
}
} // namespace xrpl

View File

@@ -79,10 +79,12 @@ enum class LedgerNameSpace : std::uint16_t {
VAULT = 'V',
LOAN_BROKER = 'l', // lower-case L
LOAN = 'L',
CONTRACT_SOURCE = 'Z',
CONTRACT = 'z',
CONTRACT_DATA = 'b',
// No longer used or supported. Left here to reserve the space
// to avoid accidental reuse.
CONTRACT [[deprecated]] = 'c',
GENERATOR [[deprecated]] = 'g',
NICKNAME [[deprecated]] = 'n',
};
@@ -530,6 +532,24 @@ permissionedDomain(uint256 const& domainID) noexcept
return {ltPERMISSIONED_DOMAIN, domainID};
}
Keylet
contractSource(uint256 const& contractHash) noexcept
{
return {ltCONTRACT_SOURCE, indexHash(LedgerNameSpace::CONTRACT_SOURCE, contractHash)};
}
Keylet
contract(uint256 const& contractHash, AccountID const& owner, std::uint32_t seq) noexcept
{
return {ltCONTRACT, indexHash(LedgerNameSpace::CONTRACT, contractHash, owner, seq)};
}
Keylet
contractData(AccountID const& owner, AccountID const& contractAccount) noexcept
{
return {ltCONTRACT_DATA, indexHash(LedgerNameSpace::CONTRACT_DATA, owner, contractAccount)};
}
} // namespace keylet
} // namespace xrpl

View File

@@ -159,6 +159,35 @@ InnerObjectFormats::InnerObjectFormats()
{sfTxnSignature, soeOPTIONAL},
{sfSigners, soeOPTIONAL},
});
add(sfFunction.jsonName.c_str(),
sfFunction.getCode(),
{
{sfFunctionName, soeREQUIRED},
{sfParameters, soeOPTIONAL},
});
add(sfInstanceParameter.jsonName,
sfInstanceParameter.getCode(),
{
{sfParameterFlag, soeREQUIRED},
{sfParameterType, soeREQUIRED},
});
add(sfInstanceParameterValue.jsonName,
sfInstanceParameterValue.getCode(),
{
{sfParameterFlag, soeREQUIRED},
{sfParameterValue, soeREQUIRED},
});
add(sfParameter.jsonName,
sfParameter.getCode(),
{
{sfParameterFlag, soeOPTIONAL},
{sfParameterType, soeOPTIONAL},
{sfParameterValue, soeOPTIONAL},
});
}
InnerObjectFormats const&

View File

@@ -0,0 +1,968 @@
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/protocol/STAccount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STBlob.h>
#include <xrpl/protocol/STCurrency.h>
#include <xrpl/protocol/STData.h>
#include <xrpl/protocol/STIssue.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/detail/STVar.h>
#include <xrpl/protocol/jss.h>
#include <cstring>
#include <stdexcept>
namespace xrpl {
template <typename U, typename S>
constexpr std::enable_if_t<std::is_unsigned<U>::value && std::is_signed<S>::value, U>
to_unsigned(S value)
{
if (value < 0 || std::numeric_limits<U>::max() < value)
Throw<std::runtime_error>("Value out of range");
return static_cast<U>(value);
}
template <typename U1, typename U2>
constexpr std::enable_if_t<std::is_unsigned<U1>::value && std::is_unsigned<U2>::value, U1>
to_unsigned(U2 value)
{
if (std::numeric_limits<U1>::max() < value)
Throw<std::runtime_error>("Value out of range");
return static_cast<U1>(value);
}
// TODO
STData::STData(SField const& n) : STBase(n), inner_type_(STI_NOTPRESENT), data_(STBase{})
{
}
STData::STData(SField const& n, unsigned char v)
: STBase(n)
, inner_type_(STI_UINT8)
, data_(detail::STVar(detail::defaultObject, sfCloseResolution))
{
setFieldUsingSetValue<STUInt8>(v);
}
STData::STData(SField const& n, std::uint16_t v)
: STBase(n)
, inner_type_(STI_UINT16)
, data_(detail::STVar(detail::defaultObject, sfSignerWeight))
{
setFieldUsingSetValue<STUInt16>(v);
}
STData::STData(SField const& n, std::uint32_t v)
: STBase(n), inner_type_(STI_UINT32), data_(detail::STVar(detail::defaultObject, sfNetworkID))
{
setFieldUsingSetValue<STUInt32>(v);
}
STData::STData(SField const& n, std::uint64_t v)
: STBase(n), inner_type_(STI_UINT64), data_(detail::STVar(detail::defaultObject, sfIndexNext))
{
setFieldUsingSetValue<STUInt64>(v);
}
STData::STData(SField const& n, uint128 const& v)
: STBase(n), inner_type_(STI_UINT128), data_(detail::STVar(detail::defaultObject, sfEmailHash))
{
setFieldUsingSetValue<STUInt128>(v);
}
STData::STData(SField const& n, uint160 const& v)
: STBase(n)
, inner_type_(STI_UINT160)
, data_(detail::STVar(detail::defaultObject, sfTakerPaysCurrency))
{
setFieldUsingSetValue<STUInt160>(v);
}
STData::STData(SField const& n, uint192 const& v)
: STBase(n)
, inner_type_(STI_UINT192)
, data_(detail::STVar(detail::defaultObject, sfMPTokenIssuanceID))
{
setFieldUsingSetValue<STUInt192>(v);
}
STData::STData(SField const& n, uint256 const& v)
: STBase(n), inner_type_(STI_UINT256), data_(detail::STVar(detail::defaultObject, sfLedgerHash))
{
setFieldUsingSetValue<STUInt256>(v);
}
STData::STData(SField const& n, Blob const& v)
: STBase(n), inner_type_(STI_VL), data_(detail::STVar(detail::defaultObject, sfURI))
{
setFieldUsingSetValue<STBlob>(Buffer(v.data(), v.size()));
}
STData::STData(SField const& n, Slice const& v)
: STBase(n), inner_type_(STI_VL), data_(detail::STVar(detail::defaultObject, sfURI))
{
setFieldUsingSetValue<STBlob>(Buffer(v.data(), v.size()));
}
STData::STData(SField const& n, STAmount const& v)
: STBase(n), inner_type_(STI_AMOUNT), data_(detail::STVar(detail::defaultObject, sfAmount))
{
setFieldUsingAssignment(v);
}
STData::STData(SField const& n, AccountID const& v)
: STBase(n), inner_type_(STI_ACCOUNT), data_(detail::STVar(detail::defaultObject, sfAccount))
{
setFieldUsingSetValue<STAccount>(v);
}
STData::STData(SField const& n, STIssue const& v)
: STBase(n), inner_type_(STI_ISSUE), data_(detail::STVar(detail::defaultObject, sfAsset))
{
setFieldUsingAssignment(v);
}
STData::STData(SField const& n, STCurrency const& v)
: STBase(n), inner_type_(STI_CURRENCY), data_(detail::STVar(detail::defaultObject, sfBaseAsset))
{
setFieldUsingAssignment(v);
}
STData::STData(SField const& n, STNumber const& v)
: STBase(n), inner_type_(STI_NUMBER), data_(detail::STVar(detail::defaultObject, sfNumber))
{
setFieldUsingAssignment(v);
}
STData::STData(SerialIter& sit, SField const& name) : STBase(name), data_(STBase{})
{
std::uint16_t stype = SerializedTypeID(sit.get16());
inner_type_ = stype;
SerializedTypeID s = static_cast<SerializedTypeID>(stype);
switch (s)
{
case STI_UINT8: {
data_ = detail::STVar(sit, sfCloseResolution);
break;
}
case STI_UINT16: {
data_ = detail::STVar(sit, sfSignerWeight);
break;
}
case STI_UINT32: {
data_ = detail::STVar(sit, sfNetworkID);
break;
}
case STI_UINT64: {
data_ = detail::STVar(sit, sfIndexNext);
break;
}
case STI_UINT128: {
data_ = detail::STVar(sit, sfEmailHash);
break;
}
case STI_UINT160: {
data_ = detail::STVar(sit, sfTakerPaysCurrency);
break;
}
case STI_UINT192: {
data_ = detail::STVar(sit, sfMPTokenIssuanceID);
break;
}
case STI_UINT256: {
data_ = detail::STVar(sit, sfLedgerHash);
break;
}
case STI_VL: {
data_ = detail::STVar(sit, sfURI);
break;
}
case STI_AMOUNT: {
data_ = detail::STVar(sit, sfAmount);
break;
}
case STI_ACCOUNT: {
data_ = detail::STVar(sit, sfAccount);
break;
}
case STI_ISSUE: {
data_ = detail::STVar(sit, sfAsset);
break;
}
case STI_CURRENCY: {
data_ = detail::STVar(sit, sfBaseAsset);
break;
}
case STI_NUMBER: {
data_ = detail::STVar(sit, sfNumber);
break;
}
default:
Throw<std::runtime_error>("STData: unknown type");
}
}
STBase*
STData::copy(std::size_t n, void* buf) const
{
return emplace(n, buf, *this);
}
STBase*
STData::move(std::size_t n, void* buf)
{
return emplace(n, buf, std::move(*this));
}
std::size_t
STData::size() const
{
switch (static_cast<SerializedTypeID>(inner_type_))
{
case STI_UINT8: {
return sizeof(uint8_t);
}
case STI_UINT16: {
return sizeof(uint16_t);
}
case STI_UINT32: {
return sizeof(uint32_t);
}
case STI_UINT64: {
return sizeof(uint64_t);
}
case STI_UINT128: {
return uint128::size();
}
case STI_UINT160: {
return uint160::size();
}
case STI_UINT192: {
return uint192::size();
}
case STI_UINT256: {
return uint256::size();
}
case STI_VL: {
STBlob const& st_blob = data_.get().downcast<STBlob>();
return st_blob.size();
}
case STI_AMOUNT: {
// TODO: STAmount::size()
STAmount const& st_amt = data_.get().downcast<STAmount>();
return st_amt.native() ? 8 : 48;
}
case STI_ACCOUNT: {
return uint160::size();
}
case STI_ISSUE: {
// const STIssue& st_issue = data_.get().downcast<STIssue>();
return 40; // 20 bytes for currency + 20 bytes for account
}
case STI_CURRENCY: {
// const STCurrency& st_currency =
// data_.get().downcast<STCurrency>();
return 20; // 20 bytes for currency
}
case STI_NUMBER: {
return sizeof(double);
}
default:
Throw<std::runtime_error>("STData: unknown type");
}
}
SerializedTypeID
STData::getSType() const
{
return STI_DATA;
}
void
STData::add(Serializer& s) const
{
s.add16(inner_type_);
switch (static_cast<SerializedTypeID>(inner_type_))
{
case STI_UINT8: {
STUInt8 const& st_uint8 = data_.get().downcast<STUInt8>();
st_uint8.add(s);
break;
}
case STI_UINT16: {
STUInt16 const& st_uint16 = data_.get().downcast<STUInt16>();
st_uint16.add(s);
break;
}
case STI_UINT32: {
STUInt32 const& st_uint32 = data_.get().downcast<STUInt32>();
st_uint32.add(s);
break;
}
case STI_UINT64: {
STUInt64 const& st_uint64 = data_.get().downcast<STUInt64>();
st_uint64.add(s);
break;
}
case STI_UINT128: {
STUInt128 const& st_uint128 = data_.get().downcast<STUInt128>();
st_uint128.add(s);
break;
}
case STI_UINT160: {
STUInt160 const& st_uint160 = data_.get().downcast<STUInt160>();
st_uint160.add(s);
break;
}
case STI_UINT192: {
STUInt192 const& st_uint192 = data_.get().downcast<STUInt192>();
st_uint192.add(s);
break;
}
case STI_UINT256: {
STUInt256 const& st_uint256 = data_.get().downcast<STUInt256>();
st_uint256.add(s);
break;
}
case STI_VL: {
STBlob const& st_blob = data_.get().downcast<STBlob>();
st_blob.add(s);
break;
}
case STI_AMOUNT: {
STAmount const& st_amt = data_.get().downcast<STAmount>();
st_amt.add(s);
break;
}
case STI_ACCOUNT: {
STAccount const& st_acc = data_.get().downcast<STAccount>();
st_acc.add(s);
break;
}
case STI_ISSUE: {
STIssue const& st_issue = data_.get().downcast<STIssue>();
st_issue.add(s);
break;
}
case STI_CURRENCY: {
STCurrency const& st_currency = data_.get().downcast<STCurrency>();
st_currency.add(s);
break;
}
case STI_NUMBER: {
STNumber const& st_number = data_.get().downcast<STNumber>();
st_number.add(s);
break;
}
default:
Throw<std::runtime_error>("STData: unknown type");
}
}
bool
STData::isEquivalent(STBase const& t) const
{
auto const* const tPtr = dynamic_cast<STData const*>(&t);
return tPtr && (default_ == tPtr->default_) && (inner_type_ == tPtr->inner_type_) &&
(data_ == tPtr->data_);
}
bool
STData::isDefault() const
{
return default_;
}
std::string
STData::getInnerTypeString() const
{
std::string inner_type_str = "Unknown";
switch (static_cast<SerializedTypeID>(inner_type_))
{
case STI_UINT8:
inner_type_str = "UINT8";
break;
case STI_UINT16:
inner_type_str = "UINT16";
break;
case STI_UINT32:
inner_type_str = "UINT32";
break;
case STI_UINT64:
inner_type_str = "UINT64";
break;
case STI_UINT128:
inner_type_str = "UINT128";
break;
case STI_UINT160:
inner_type_str = "UINT160";
break;
case STI_UINT192:
inner_type_str = "UINT192";
break;
case STI_UINT256:
inner_type_str = "UINT256";
break;
case STI_VL:
inner_type_str = "VL";
break;
case STI_AMOUNT:
inner_type_str = "AMOUNT";
break;
case STI_ACCOUNT:
inner_type_str = "ACCOUNT";
break;
case STI_ISSUE:
inner_type_str = "ISSUE";
break;
case STI_CURRENCY:
inner_type_str = "CURRENCY";
break;
case STI_NUMBER:
inner_type_str = "NUMBER";
break;
// Add other known types as needed
default:
inner_type_str = std::to_string(inner_type_);
}
return inner_type_str;
}
std::string
STData::getText() const
{
std::string inner_type_str = getInnerTypeString();
return "STData{InnerType: " + inner_type_str + ", Data: " + data_.get().getText() + "}";
}
Json::Value
STData::getJson(JsonOptions options) const
{
Json::Value ret(Json::objectValue);
ret[jss::type] = getInnerTypeString();
ret[jss::value] = data_.get().getJson(options);
return ret;
}
STBase*
STData::makeFieldPresent()
{
STBase* f = &data_.get(); // getPIndex(index);
if (f->getSType() != STI_NOTPRESENT)
return f;
data_ = detail::STVar(detail::nonPresentObject, f->getFName());
return &data_.get();
}
void
STData::setFieldU8(unsigned char v)
{
inner_type_ = STI_UINT8;
data_ = detail::STVar(detail::defaultObject, sfCloseResolution);
setFieldUsingSetValue<STUInt8>(v);
}
void
STData::setFieldU16(std::uint16_t v)
{
inner_type_ = STI_UINT16;
data_ = detail::STVar(detail::defaultObject, sfSignerWeight);
setFieldUsingSetValue<STUInt16>(v);
}
void
STData::setFieldU32(std::uint32_t v)
{
inner_type_ = STI_UINT32;
data_ = detail::STVar(detail::defaultObject, sfNetworkID);
setFieldUsingSetValue<STUInt32>(v);
}
void
STData::setFieldU64(std::uint64_t v)
{
inner_type_ = STI_UINT64;
data_ = detail::STVar(detail::defaultObject, sfIndexNext);
setFieldUsingSetValue<STUInt64>(v);
}
void
STData::setFieldH128(uint128 const& v)
{
inner_type_ = STI_UINT128;
data_ = detail::STVar(detail::defaultObject, sfEmailHash);
setFieldUsingSetValue<STUInt128>(v);
}
void
STData::setFieldH160(uint160 const& v)
{
inner_type_ = STI_UINT160;
data_ = detail::STVar(detail::defaultObject, sfTakerPaysCurrency);
setFieldUsingSetValue<STUInt160>(v);
}
void
STData::setFieldH192(uint192 const& v)
{
inner_type_ = STI_UINT192;
data_ = detail::STVar(detail::defaultObject, sfMPTokenIssuanceID);
setFieldUsingSetValue<STUInt192>(v);
}
void
STData::setFieldH256(uint256 const& v)
{
inner_type_ = STI_UINT256;
data_ = detail::STVar(detail::defaultObject, sfLedgerHash);
setFieldUsingSetValue<STUInt256>(v);
}
void
STData::setFieldVL(Blob const& v)
{
inner_type_ = STI_VL;
data_ = detail::STVar(detail::defaultObject, sfData);
setFieldUsingSetValue<STBlob>(Buffer(v.data(), v.size()));
}
void
STData::setFieldVL(Slice const& s)
{
inner_type_ = STI_VL;
data_ = detail::STVar(detail::defaultObject, sfData);
setFieldUsingSetValue<STBlob>(Buffer(s.data(), s.size()));
}
void
STData::setAccountID(AccountID const& v)
{
inner_type_ = STI_ACCOUNT;
data_ = detail::STVar(detail::defaultObject, sfAccount);
setFieldUsingSetValue<STAccount>(v);
}
void
STData::setFieldAmount(STAmount const& v)
{
inner_type_ = STI_AMOUNT;
data_ = detail::STVar(detail::defaultObject, sfAmount);
setFieldUsingAssignment(v);
}
void
STData::setIssue(STIssue const& v)
{
inner_type_ = STI_ISSUE;
data_ = detail::STVar(detail::defaultObject, sfAsset);
setFieldUsingAssignment(v);
}
void
STData::setCurrency(STCurrency const& v)
{
inner_type_ = STI_CURRENCY;
data_ = detail::STVar(detail::defaultObject, sfBaseAsset);
setFieldUsingAssignment(v);
}
void
STData::setFieldNumber(STNumber const& v)
{
inner_type_ = STI_NUMBER;
data_ = detail::STVar(detail::defaultObject, sfNumber);
setFieldUsingAssignment(v);
}
unsigned char
STData::getFieldU8() const
{
return getFieldByValue<STUInt8>();
}
std::uint16_t
STData::getFieldU16() const
{
return getFieldByValue<STUInt16>();
}
std::uint32_t
STData::getFieldU32() const
{
return getFieldByValue<STUInt32>();
}
std::uint64_t
STData::getFieldU64() const
{
return getFieldByValue<STUInt64>();
}
uint128
STData::getFieldH128() const
{
return getFieldByValue<STUInt128>();
}
uint160
STData::getFieldH160() const
{
return getFieldByValue<STUInt160>();
}
uint192
STData::getFieldH192() const
{
return getFieldByValue<STUInt192>();
}
uint256
STData::getFieldH256() const
{
return getFieldByValue<STUInt256>();
}
Blob
STData::getFieldVL() const
{
STBlob empty;
STBlob const& b = getFieldByConstRef<STBlob>(empty);
return Blob(b.data(), b.data() + b.size());
}
AccountID
STData::getAccountID() const
{
return getFieldByValue<STAccount>();
}
STAmount const&
STData::getFieldAmount() const
{
static STAmount const empty{};
return getFieldByConstRef<STAmount>(empty);
}
STIssue
STData::getFieldIssue() const
{
static STIssue const empty{};
return getFieldByConstRef<STIssue>(empty);
}
STCurrency
STData::getFieldCurrency() const
{
static STCurrency const empty{};
return getFieldByConstRef<STCurrency>(empty);
}
STNumber
STData::getFieldNumber() const
{
static STNumber const empty{};
return getFieldByConstRef<STNumber>(empty);
}
STData
dataFromJson(SField const& field, Json::Value const& v)
{
Json::Value type;
Json::Value value;
if (!v.isObject())
Throw<std::runtime_error>("STData: expected object");
type = v[jss::type];
value = v[jss::value];
if (type.isNull())
Throw<std::runtime_error>("STData: type is null");
if (value.isNull())
Throw<std::runtime_error>("STData: value is null");
auto typeStr = type.asString();
if (typeStr == "UINT8")
{
STData data(field, static_cast<unsigned char>(value.asUInt()));
return data;
}
else if (typeStr == "UINT16")
{
STData data(field, static_cast<std::uint16_t>(value.asUInt()));
return data;
}
else if (typeStr == "UINT32")
{
try
{
if (value.isString())
{
STData data(field, beast::lexicalCastThrow<std::uint32_t>(value.asString()));
return data;
}
else if (value.isInt())
{
STData data(field, to_unsigned<std::uint32_t>(value.asInt()));
return data;
}
else if (value.isUInt())
{
STData data(field, safe_cast<std::uint32_t>(value.asUInt()));
return data;
}
else
{
Throw<std::runtime_error>("bad type for UINT32");
}
}
catch (std::exception const&)
{
Throw<std::runtime_error>("invalid data for UINT32");
}
}
else if (typeStr == "UINT64")
{
try
{
if (value.isString())
{
auto const str = value.asString();
std::uint64_t val;
bool const useBase10 = field.shouldMeta(SField::sMD_BaseTen);
// if the field is amount, serialize as base 10
auto [p, ec] =
std::from_chars(str.data(), str.data() + str.size(), val, useBase10 ? 10 : 16);
if (ec != std::errc() || (p != str.data() + str.size()))
Throw<std::invalid_argument>("STData: invalid UINT64 data");
STData data(field, val);
return data;
}
else if (value.isInt())
{
STData data(field, to_unsigned<std::uint64_t>(value.asInt()));
return data;
}
else if (value.isUInt())
{
STData data(field, safe_cast<std::uint64_t>(value.asUInt()));
return data;
}
else
{
Throw<std::runtime_error>("STData: bad type for UINT64");
}
}
catch (std::exception const&)
{
Throw<std::runtime_error>("STData: invalid data for UINT64");
}
}
else if (typeStr == "UINT128")
{
if (!value.isString())
{
Throw<std::runtime_error>("STData: expected string for UINT128");
}
uint128 num;
if (auto const s = value.asString(); !num.parseHex(s))
{
if (!s.empty())
{
Throw<std::runtime_error>("STData: invalid UINT128 data");
}
num.zero();
}
STData data(field, num);
return data;
}
else if (typeStr == "UINT192")
{
if (!value.isString())
{
Throw<std::runtime_error>("STData: expected string for UINT192");
}
uint192 num;
if (auto const s = value.asString(); !num.parseHex(s))
{
if (!s.empty())
{
Throw<std::runtime_error>("STData: invalid UINT192 data");
}
num.zero();
}
STData data(field, num);
return data;
}
else if (typeStr == "UINT160")
{
if (!value.isString())
{
Throw<std::runtime_error>("STData: expected string for UINT160");
}
uint160 num;
if (auto const s = value.asString(); !num.parseHex(s))
{
if (!s.empty())
{
Throw<std::runtime_error>("STData: invalid UINT160 data");
}
num.zero();
}
STData data(field, num);
return data;
}
else if (typeStr == "UINT256")
{
if (!value.isString())
{
Throw<std::runtime_error>("STData: expected string for UINT256");
}
uint256 num;
if (auto const s = value.asString(); !num.parseHex(s))
{
if (!s.empty())
{
Throw<std::runtime_error>("STData: invalid UINT256 data");
}
num.zero();
}
STData data(field, num);
return data;
}
else if (typeStr == "VL")
{
if (!value.isString())
{
Throw<std::runtime_error>("STData: expected string for VL");
}
try
{
if (auto vBlob = strUnHex(value.asString()))
{
STData data(field, *vBlob);
return data;
}
else
{
Throw<std::invalid_argument>("invalid data");
}
}
catch (std::exception const&)
{
Throw<std::runtime_error>("STData: invalid data");
}
}
else if (typeStr == "AMOUNT")
{
try
{
STData data(field, amountFromJson(field, value));
return data;
}
catch (std::exception const&)
{
Throw<std::runtime_error>("STData: invalid data for AMOUNT");
}
}
else if (typeStr == "ACCOUNT")
{
if (!value.isString())
{
Throw<std::runtime_error>("STData: expected string for ACCOUNT");
}
std::string const strValue = value.asString();
try
{
if (AccountID account; account.parseHex(strValue))
{
STData data(field, account);
return data;
}
if (auto result = parseBase58<AccountID>(strValue))
{
STData data(field, *result);
return data;
}
Throw<std::runtime_error>("STData: invalid data for ACCOUNT");
}
catch (std::exception const&)
{
Throw<std::runtime_error>("STData: invalid data for ACCOUNT");
}
}
else if (typeStr == "ISSUE")
{
try
{
STData data(field, issueFromJson(field, value));
return data;
}
catch (std::exception const&)
{
Throw<std::runtime_error>("STData: invalid data for ISSUE");
}
}
else if (typeStr == "CURRENCY")
{
try
{
STData data(field, currencyFromJson(field, value));
return data;
}
catch (std::exception const&)
{
Throw<std::runtime_error>("STData: invalid data for CURRENCY");
}
}
else if (typeStr == "NUMBER")
{
if (!value.isString())
{
Throw<std::runtime_error>("STData: expected string for NUMBER");
}
STNumber number = numberFromJson(field, value);
STData data(field, number);
return data;
}
// Handle unknown or unsupported type
Throw<std::runtime_error>("STData: unsupported type string: " + typeStr);
}
} // namespace xrpl

View File

@@ -0,0 +1,253 @@
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/protocol/STAccount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STBlob.h>
#include <xrpl/protocol/STDataType.h>
#include <xrpl/protocol/detail/STVar.h>
#include <xrpl/protocol/jss.h>
#include <cstring>
#include <stdexcept>
namespace xrpl {
// TODO
STDataType::STDataType(SField const& n) : STBase(n), inner_type_(STI_NOTPRESENT)
{
}
STDataType::STDataType(SField const& n, SerializedTypeID v)
: STBase(n), inner_type_(v), default_(false)
{
}
STDataType::STDataType(SerialIter& sit, SField const& name)
: STBase(name), inner_type_(STI_DATA), default_(false)
{
std::uint16_t stype = SerializedTypeID(sit.get16());
inner_type_ = stype;
}
STBase*
STDataType::copy(std::size_t n, void* buf) const
{
return emplace(n, buf, *this);
}
STBase*
STDataType::move(std::size_t n, void* buf)
{
return emplace(n, buf, std::move(*this));
}
SerializedTypeID
STDataType::getSType() const
{
return STI_DATATYPE;
}
void
STDataType::setInnerSType(SerializedTypeID v)
{
inner_type_ = v;
}
void
STDataType::add(Serializer& s) const
{
s.add16(inner_type_);
}
bool
STDataType::isEquivalent(STBase const& t) const
{
auto const* const tPtr = dynamic_cast<STDataType const*>(&t);
return tPtr && (default_ == tPtr->default_) && (inner_type_ == tPtr->inner_type_);
}
bool
STDataType::isDefault() const
{
return default_;
}
std::string
STDataType::getInnerTypeString() const
{
std::string inner_type_str = "Unknown";
// Optionally, convert inner_type_ to its string representation if mappings
// exist
switch (static_cast<SerializedTypeID>(inner_type_))
{
case STI_UINT8:
inner_type_str = "UINT8";
break;
case STI_UINT16:
inner_type_str = "UINT16";
break;
case STI_UINT32:
inner_type_str = "UINT32";
break;
case STI_UINT64:
inner_type_str = "UINT64";
break;
case STI_UINT128:
inner_type_str = "UINT128";
break;
case STI_UINT160:
inner_type_str = "UINT160";
break;
case STI_UINT192:
inner_type_str = "UINT192";
break;
case STI_UINT256:
inner_type_str = "UINT256";
break;
case STI_VL:
inner_type_str = "VL";
break;
case STI_ACCOUNT:
inner_type_str = "ACCOUNT";
break;
case STI_AMOUNT:
inner_type_str = "AMOUNT";
break;
case STI_ISSUE:
inner_type_str = "ISSUE";
break;
case STI_CURRENCY:
inner_type_str = "CURRENCY";
break;
case STI_NUMBER:
inner_type_str = "NUMBER";
break;
// Add other known types as needed
default:
inner_type_str = std::to_string(inner_type_);
}
return inner_type_str;
}
std::string
STDataType::getText() const
{
std::string inner_type_str = getInnerTypeString();
return "STDataType{InnerType: " + inner_type_str + "}";
}
Json::Value
STDataType::getJson(JsonOptions) const
{
Json::Value ret(Json::objectValue);
ret[jss::type] = getInnerTypeString();
return ret;
}
STDataType
dataTypeFromJson(SField const& field, Json::Value const& v)
{
SerializedTypeID typeId = STI_NOTPRESENT;
Json::Value type;
Json::Value value;
if (!v.isObject())
{
Throw<std::runtime_error>("STData: expected object");
}
type = v[jss::type];
auto typeStr = type.asString();
if (typeStr == "UINT8")
{
typeId = STI_UINT8;
STDataType data(field, typeId);
return data;
}
else if (typeStr == "UINT16")
{
typeId = STI_UINT16;
STDataType data(field, typeId);
return data;
}
else if (typeStr == "UINT32")
{
typeId = STI_UINT32;
STDataType data(field, typeId);
return data;
}
else if (typeStr == "UINT64")
{
typeId = STI_UINT64;
STDataType data(field, typeId);
return data;
}
else if (typeStr == "UINT128")
{
typeId = STI_UINT128;
STDataType data(field, typeId);
return data;
}
else if (typeStr == "UINT160")
{
typeId = STI_UINT160;
STDataType data(field, typeId);
return data;
}
else if (typeStr == "UINT192")
{
typeId = STI_UINT192;
STDataType data(field, typeId);
return data;
}
else if (typeStr == "UINT256")
{
typeId = STI_UINT256;
STDataType data(field, typeId);
return data;
}
else if (typeStr == "VL")
{
typeId = STI_VL;
STDataType data(field, typeId);
return data;
}
else if (typeStr == "ACCOUNT")
{
typeId = STI_ACCOUNT;
STDataType data(field, typeId);
return data;
}
else if (typeStr == "AMOUNT")
{
typeId = STI_AMOUNT;
STDataType data(field, typeId);
return data;
}
else if (typeStr == "ISSUE")
{
typeId = STI_ISSUE;
STDataType data(field, typeId);
return data;
}
else if (typeStr == "CURRENCY")
{
typeId = STI_CURRENCY;
STDataType data(field, typeId);
return data;
}
else if (typeStr == "NUMBER")
{
typeId = STI_NUMBER;
STDataType data(field, typeId);
return data;
}
// Handle unknown or unsupported type
Throw<std::runtime_error>("STData: unsupported type string: " + typeStr);
}
} // namespace xrpl

View File

@@ -0,0 +1,758 @@
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAccount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/STBlob.h>
#include <xrpl/protocol/STCurrency.h>
#include <xrpl/protocol/STData.h>
#include <xrpl/protocol/STDataType.h>
#include <xrpl/protocol/STInteger.h>
#include <xrpl/protocol/STJson.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STPathSet.h>
#include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/Serializer.h>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <stdexcept>
namespace xrpl {
STJson::STJson(SField const& name) : STBase{name}, data_{Map{}}
{
}
STJson::STJson(SerialIter& sit, SField const& name) : STBase{name}
{
if (sit.empty())
{
data_ = Map{};
return;
}
int length = sit.getVLDataLength();
if (length < 0)
Throw<std::runtime_error>("Invalid STJson length");
if (length == 0)
{
data_ = Map{};
return;
}
// Read type byte
auto typeByte = sit.get8();
JsonType type = static_cast<JsonType>(typeByte);
length--; // Account for type byte
int initialBytesLeft = sit.getBytesLeft();
if (type == JsonType::Array)
{
Array array;
while (sit.getBytesLeft() > 0 && (initialBytesLeft - sit.getBytesLeft()) < length)
{
auto valueVL = sit.getVL();
if (!valueVL.empty())
{
SerialIter valueSit(valueVL.data(), valueVL.size());
auto value = makeValueFromVLWithType(valueSit);
array.push_back(std::move(value));
}
else
{
array.push_back(nullptr);
}
}
data_ = std::move(array);
}
else // JsonType::Object
{
Map map;
while (sit.getBytesLeft() > 0 && (initialBytesLeft - sit.getBytesLeft()) < length)
{
auto [key, value] = parsePair(sit);
map.emplace(std::move(key), std::move(value));
}
data_ = std::move(map);
}
int consumedBytes = initialBytesLeft - sit.getBytesLeft();
if (consumedBytes != length)
Throw<std::runtime_error>("STJson length mismatch");
}
STJson::STJson(Map&& map) : data_(std::move(map))
{
}
STJson::STJson(Array&& array) : data_(std::move(array))
{
}
SerializedTypeID
STJson::getSType() const
{
return STI_JSON;
}
bool
STJson::isArray() const
{
return std::holds_alternative<Array>(data_);
}
bool
STJson::isObject() const
{
return std::holds_alternative<Map>(data_);
}
STJson::JsonType
STJson::getType() const
{
return isArray() ? JsonType::Array : JsonType::Object;
}
int
STJson::getDepth() const
{
if (isArray())
{
auto const& array = std::get<Array>(data_);
for (auto const& value : array)
{
if (value)
{
auto nested = std::dynamic_pointer_cast<STJson>(value);
if (nested)
return 1 + nested->getDepth();
}
}
return 0;
}
else // isObject()
{
auto const& map = std::get<Map>(data_);
for (auto const& [key, value] : map)
{
if (value)
{
auto nested = std::dynamic_pointer_cast<STJson>(value);
if (nested)
return 1 + nested->getDepth();
}
}
return 0;
}
}
void
STJson::validateDepth(Value const& value, int currentDepth) const
{
if (!value)
return;
auto nested = std::dynamic_pointer_cast<STJson>(value);
if (!nested)
return;
int valueDepth = nested->getDepth();
if (currentDepth + valueDepth > 1)
Throw<std::runtime_error>("STJson nesting depth exceeds maximum of 1");
}
void
STJson::setObjectField(Key const& key, Value const& value)
{
if (!isObject())
Throw<std::runtime_error>("STJson::setObjectField called on non-object");
validateDepth(value, 0);
std::get<Map>(data_)[key] = value;
}
std::shared_ptr<STJson>
STJson::fromBlob(void const* data, std::size_t size)
{
SerialIter sit(static_cast<uint8_t const*>(data), size);
return fromSerialIter(sit);
}
std::shared_ptr<STJson>
STJson::fromSerialIter(SerialIter& sit)
{
if (sit.empty())
return nullptr;
int length = sit.getVLDataLength();
if (length < 0)
Throw<std::runtime_error>("Invalid STJson length");
if (length == 0)
return std::make_shared<STJson>(Map{});
// Read type byte
auto typeByte = sit.get8();
JsonType type = static_cast<JsonType>(typeByte);
length--; // Account for type byte
int initialBytesLeft = sit.getBytesLeft();
if (type == JsonType::Array)
{
Array array;
while (sit.getBytesLeft() > 0 && (initialBytesLeft - sit.getBytesLeft()) < length)
{
auto valueVL = sit.getVL();
if (!valueVL.empty())
{
SerialIter valueSit(valueVL.data(), valueVL.size());
auto value = makeValueFromVLWithType(valueSit);
array.push_back(std::move(value));
}
else
{
array.push_back(nullptr);
}
}
int consumedBytes = initialBytesLeft - sit.getBytesLeft();
if (consumedBytes != length)
Throw<std::runtime_error>("STJson length mismatch");
return std::make_shared<STJson>(std::move(array));
}
else // JsonType::Object
{
Map map;
while (sit.getBytesLeft() > 0 && (initialBytesLeft - sit.getBytesLeft()) < length)
{
auto [key, value] = parsePair(sit);
map.emplace(std::move(key), std::move(value));
}
int consumedBytes = initialBytesLeft - sit.getBytesLeft();
if (consumedBytes != length)
Throw<std::runtime_error>("STJson length mismatch");
return std::make_shared<STJson>(std::move(map));
}
}
std::pair<STJson::Key, STJson::Value>
STJson::parsePair(SerialIter& sit)
{
auto keyBlob = sit.getVL();
std::string key(reinterpret_cast<char const*>(keyBlob.data()), keyBlob.size());
auto valueVL = sit.getVL();
if (valueVL.empty())
return {std::move(key), nullptr};
SerialIter valueSit(valueVL.data(), valueVL.size());
auto value = makeValueFromVLWithType(valueSit);
return {std::move(key), std::move(value)};
}
STJson::Array
STJson::parseArray(SerialIter& sit, int length)
{
Array array;
int initialBytesLeft = sit.getBytesLeft();
while (sit.getBytesLeft() > 0 && (initialBytesLeft - sit.getBytesLeft()) < length)
{
auto valueVL = sit.getVL();
if (!valueVL.empty())
{
SerialIter valueSit(valueVL.data(), valueVL.size());
auto value = makeValueFromVLWithType(valueSit);
array.push_back(std::move(value));
}
else
{
array.push_back(nullptr);
}
}
return array;
}
STJson::Value
STJson::makeValueFromVLWithType(SerialIter& sit)
{
if (sit.getBytesLeft() == 0)
return nullptr;
// Read SType marker (1 byte)
auto typeCode = sit.get8();
SerializedTypeID stype = static_cast<SerializedTypeID>(typeCode);
// Dispatch to correct SType
switch (stype)
{
case STI_UINT8:
return std::make_shared<STUInt8>(sfCloseResolution, sit.get8());
case STI_UINT16:
return std::make_shared<STUInt16>(sfSignerWeight, sit.get16());
case STI_UINT32:
return std::make_shared<STUInt32>(sfNetworkID, sit.get32());
case STI_UINT64:
return std::make_shared<STUInt64>(sfIndexNext, sit.get64());
case STI_UINT128:
return std::make_shared<STUInt128>(sfEmailHash, sit.get128());
case STI_UINT160:
return std::make_shared<STUInt160>(sfTakerPaysCurrency, sit.get160());
case STI_UINT192:
return std::make_shared<STUInt192>(sfMPTokenIssuanceID, sit.get192());
case STI_UINT256:
return std::make_shared<STUInt256>(sfLedgerHash, sit.get256());
case STI_VL: {
auto blob = sit.getVL();
return std::make_shared<STBlob>(sfData, blob.data(), blob.size());
}
case STI_ACCOUNT:
return std::make_shared<STAccount>(sit, sfAccount);
case STI_AMOUNT:
return std::make_shared<STAmount>(sit, sfAmount);
// case STI_NUMBER:
// return std::make_shared<STNumber>(sit, sfNumber);
case STI_ISSUE:
return std::make_shared<STIssue>(sit, sfAsset);
case STI_CURRENCY:
return std::make_shared<STCurrency>(sit, sfBaseAsset);
case STI_JSON:
return std::make_shared<STJson>(sit, sfContractJson);
case STI_OBJECT:
case STI_ARRAY:
case STI_PATHSET:
case STI_VECTOR256:
default:
// Unknown type, treat as blob
{
auto blob = sit.getSlice(sit.getBytesLeft());
return std::make_shared<STBlob>(sfData, blob.data(), blob.size());
}
}
}
std::optional<STJson::Value>
STJson::getObjectField(Key const& key) const
{
if (!isObject())
return std::nullopt;
auto const& map = std::get<Map>(data_);
auto it = map.find(key);
if (it == map.end() || !it->second)
return std::nullopt;
return it->second;
}
void
STJson::setNestedObjectField(Key const& key, Key const& nestedKey, Value const& value)
{
if (!isObject())
Throw<std::runtime_error>("STJson::setNestedObjectField called on non-object");
validateDepth(value, 1); // We're at depth 1 (nested)
auto& map = std::get<Map>(data_);
auto it = map.find(key);
std::shared_ptr<STJson> nested;
if (it == map.end() || !it->second)
{
// Create new nested STJson
nested = std::make_shared<STJson>();
map[key] = nested;
}
else
{
nested = std::dynamic_pointer_cast<STJson>(it->second);
if (!nested)
{
// Overwrite with new STJson if not an STJson
nested = std::make_shared<STJson>();
map[key] = nested;
}
}
nested->setObjectField(nestedKey, value);
}
std::optional<STJson::Value>
STJson::getNestedObjectField(Key const& key, Key const& nestedKey) const
{
if (!isObject())
return std::nullopt;
auto const& map = std::get<Map>(data_);
auto it = map.find(key);
if (it == map.end() || !it->second)
return std::nullopt;
auto nested = std::dynamic_pointer_cast<STJson>(it->second);
if (!nested)
return std::nullopt;
return nested->getObjectField(nestedKey);
}
STJson::Map const&
STJson::getMap() const
{
if (!isObject())
Throw<std::runtime_error>("STJson::getMap called on non-object");
return std::get<Map>(data_);
}
STJson::Array const&
STJson::getArray() const
{
if (!isArray())
Throw<std::runtime_error>("STJson::getArray called on non-array");
return std::get<Array>(data_);
}
void
STJson::pushArrayElement(Value const& value)
{
if (!isArray())
Throw<std::runtime_error>("STJson::pushArrayElement called on non-array");
validateDepth(value, 0);
std::get<Array>(data_).push_back(value);
}
std::optional<STJson::Value>
STJson::getArrayElement(size_t index) const
{
if (!isArray())
return std::nullopt;
auto const& array = std::get<Array>(data_);
if (index >= array.size())
return std::nullopt;
return array[index];
}
void
STJson::setArrayElement(size_t index, Value const& value)
{
if (!isArray())
Throw<std::runtime_error>("STJson::setArrayElement called on non-array");
validateDepth(value, 0);
auto& array = std::get<Array>(data_);
// Auto-resize with nulls if needed
if (index >= array.size())
array.resize(index + 1, nullptr);
array[index] = value;
}
void
STJson::setArrayElementField(size_t index, Key const& key, Value const& value)
{
if (!isArray())
Throw<std::runtime_error>("STJson::setArrayElementField called on non-array");
validateDepth(value, 1); // We're at depth 1 (inside array element)
auto& array = std::get<Array>(data_);
// Auto-resize with nulls if needed
if (index >= array.size())
array.resize(index + 1, nullptr);
// Get or create STJson object at index
std::shared_ptr<STJson> element;
if (!array[index])
{
element = std::make_shared<STJson>(Map{});
array[index] = element;
}
else
{
element = std::dynamic_pointer_cast<STJson>(array[index]);
if (!element)
{
// Replace with new STJson if not an STJson
element = std::make_shared<STJson>(Map{});
array[index] = element;
}
}
element->setObjectField(key, value);
}
std::optional<STJson::Value>
STJson::getArrayElementField(size_t index, Key const& key) const
{
if (!isArray())
return std::nullopt;
auto const& array = std::get<Array>(data_);
if (index >= array.size())
return std::nullopt;
auto element = std::dynamic_pointer_cast<STJson>(array[index]);
if (!element)
return std::nullopt;
return element->getObjectField(key);
}
size_t
STJson::arraySize() const
{
if (!isArray())
return 0;
return std::get<Array>(data_).size();
}
void
STJson::setNestedArrayElement(Key const& key, size_t index, Value const& value)
{
if (!isObject())
Throw<std::runtime_error>("STJson::setNestedArrayElement called on non-object");
validateDepth(value, 1); // We're at depth 1 (nested array)
auto& map = std::get<Map>(data_);
auto it = map.find(key);
std::shared_ptr<STJson> arrayJson;
if (it == map.end() || !it->second)
{
// Create new nested STJson array
arrayJson = std::make_shared<STJson>(Array{});
map[key] = arrayJson;
}
else
{
arrayJson = std::dynamic_pointer_cast<STJson>(it->second);
if (!arrayJson)
{
// Replace with new STJson array if not an STJson
arrayJson = std::make_shared<STJson>(Array{});
map[key] = arrayJson;
}
else if (!arrayJson->isArray())
{
// Replace with array if not an array
arrayJson = std::make_shared<STJson>(Array{});
map[key] = arrayJson;
}
}
arrayJson->setArrayElement(index, value);
}
void
STJson::setNestedArrayElementField(
Key const& key,
size_t index,
Key const& nestedKey,
Value const& value)
{
if (!isObject())
Throw<std::runtime_error>("STJson::setNestedArrayElementField called on non-object");
validateDepth(value, 1); // We're at depth 1 (nested array element field -
// still counts as depth 1)
auto& map = std::get<Map>(data_);
auto it = map.find(key);
std::shared_ptr<STJson> arrayJson;
if (it == map.end() || !it->second)
{
// Create new nested STJson array
arrayJson = std::make_shared<STJson>(Array{});
map[key] = arrayJson;
}
else
{
arrayJson = std::dynamic_pointer_cast<STJson>(it->second);
if (!arrayJson)
{
// Replace with new STJson array if not an STJson
arrayJson = std::make_shared<STJson>(Array{});
map[key] = arrayJson;
}
else if (!arrayJson->isArray())
{
// Replace with array if not an array
arrayJson = std::make_shared<STJson>(Array{});
map[key] = arrayJson;
}
}
arrayJson->setArrayElementField(index, nestedKey, value);
}
std::optional<STJson::Value>
STJson::getNestedArrayElement(Key const& key, size_t index) const
{
if (!isObject())
return std::nullopt;
auto const& map = std::get<Map>(data_);
auto it = map.find(key);
if (it == map.end() || !it->second)
return std::nullopt;
auto arrayJson = std::dynamic_pointer_cast<STJson>(it->second);
if (!arrayJson || !arrayJson->isArray())
return std::nullopt;
return arrayJson->getArrayElement(index);
}
std::optional<STJson::Value>
STJson::getNestedArrayElementField(Key const& key, size_t index, Key const& nestedKey) const
{
if (!isObject())
return std::nullopt;
auto const& map = std::get<Map>(data_);
auto it = map.find(key);
if (it == map.end() || !it->second)
return std::nullopt;
auto arrayJson = std::dynamic_pointer_cast<STJson>(it->second);
if (!arrayJson || !arrayJson->isArray())
return std::nullopt;
return arrayJson->getArrayElementField(index, nestedKey);
}
void
STJson::addVLKey(Serializer& s, std::string const& str)
{
s.addVL(str.data(), str.size());
}
void
STJson::addVLValue(Serializer& s, std::shared_ptr<STBase> const& value)
{
if (!value)
{
s.addVL(nullptr, 0);
return;
}
Serializer tmp;
tmp.add8(static_cast<uint8_t>(value->getSType()));
value->add(tmp);
s.addVL(tmp.peekData().data(), tmp.peekData().size());
}
void
STJson::add(Serializer& s) const
{
Serializer inner;
// Add type byte
inner.add8(static_cast<uint8_t>(getType()));
if (isArray())
{
auto const& array = std::get<Array>(data_);
for (auto const& value : array)
{
addVLValue(inner, value);
}
}
else // isObject()
{
auto const& map = std::get<Map>(data_);
for (auto const& [key, value] : map)
{
addVLKey(inner, key);
addVLValue(inner, value);
}
}
s.addVL(inner.peekData().data(), inner.peekData().size());
}
Json::Value
STJson::getJson(JsonOptions options) const
{
if (isArray())
{
Json::Value arr(Json::arrayValue);
auto const& array = std::get<Array>(data_);
for (auto const& value : array)
{
if (value)
arr.append(value->getJson(options));
else
arr.append(Json::nullValue);
}
return arr;
}
else // isObject()
{
Json::Value obj(Json::objectValue);
auto const& map = std::get<Map>(data_);
for (auto const& [key, value] : map)
{
if (value)
obj[key] = value->getJson(options);
else
obj[key] = Json::nullValue;
}
return obj;
}
}
bool
STJson::isEquivalent(STBase const& t) const
{
auto const* const tPtr = dynamic_cast<STJson const*>(&t);
return tPtr && (data_ == tPtr->data_);
}
bool
STJson::isDefault() const
{
return default_;
}
Blob
STJson::toBlob() const
{
Serializer s;
add(s);
return s.peekData();
}
std::size_t
STJson::size() const
{
Serializer s;
add(s);
return s.size();
}
void
STJson::setValue(STJson const& v)
{
data_ = v.data_;
}
STBase*
STJson::copy(std::size_t n, void* buf) const
{
return emplace(n, buf, *this);
}
STBase*
STJson::move(std::size_t n, void* buf)
{
return emplace(n, buf, std::move(*this));
}
} // namespace xrpl

View File

@@ -19,8 +19,11 @@
#include <xrpl/protocol/STBitString.h>
#include <xrpl/protocol/STBlob.h>
#include <xrpl/protocol/STCurrency.h>
#include <xrpl/protocol/STData.h>
#include <xrpl/protocol/STDataType.h>
#include <xrpl/protocol/STInteger.h>
#include <xrpl/protocol/STIssue.h>
#include <xrpl/protocol/STJson.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STPathSet.h>
@@ -637,6 +640,20 @@ STObject::getAccountID(SField const& field) const
return getFieldByValue<STAccount>(field);
}
STData
STObject::getFieldData(SField const& field) const
{
static STData const empty{field};
return getFieldByConstRef<STData>(field, empty);
}
STDataType
STObject::getFieldDataType(SField const& field) const
{
static STDataType const empty{field};
return getFieldByConstRef<STDataType>(field, empty);
}
Blob
STObject::getFieldVL(SField const& field) const
{
@@ -697,6 +714,13 @@ STObject::getFieldNumber(SField const& field) const
return getFieldByConstRef<STNumber>(field, empty);
}
STJson const&
STObject::getFieldJson(SField const& field) const
{
static STJson const empty{field};
return getFieldByConstRef<STJson>(field, empty);
}
void
STObject::set(std::unique_ptr<STBase> v)
{
@@ -719,6 +743,69 @@ STObject::set(STBase&& v)
}
}
void
STObject::addFieldFromSlice(SField const& sfield, Slice const& data)
{
SerialIter sit(data.data(), data.size());
std::unique_ptr<STBase> element;
switch (sfield.fieldType)
{
case STI_AMOUNT:
element = std::make_unique<STAmount>(sit, sfield);
break;
case STI_ACCOUNT:
element = std::make_unique<STAccount>(sit, sfield);
break;
case STI_UINT8:
element = std::make_unique<STUInt8>(sit, sfield);
break;
case STI_UINT16:
element = std::make_unique<STUInt16>(sit, sfield);
break;
case STI_UINT32:
element = std::make_unique<STUInt32>(sit, sfield);
break;
case STI_UINT64:
element = std::make_unique<STUInt64>(sit, sfield);
break;
case STI_UINT128:
element = std::make_unique<STUInt128>(sit, sfield);
break;
case STI_UINT160:
element = std::make_unique<STUInt160>(sit, sfield);
break;
case STI_UINT256:
element = std::make_unique<STUInt256>(sit, sfield);
break;
case STI_VECTOR256:
element = std::make_unique<STVector256>(sit, sfield);
break;
case STI_VL:
element = std::make_unique<STBlob>(sit, sfield);
break;
case STI_CURRENCY:
element = std::make_unique<STCurrency>(sit, sfield);
break;
case STI_ISSUE:
element = std::make_unique<STIssue>(sit, sfield);
break;
case STI_PATHSET:
element = std::make_unique<STPathSet>(sit, sfield);
break;
case STI_ARRAY:
element = std::make_unique<STArray>(sit, sfield);
break;
case STI_OBJECT:
element = std::make_unique<STObject>(sit, sfield, 0);
break;
default:
throw std::runtime_error("Unsupported SField type");
}
this->set(std::move(element));
}
void
STObject::setFieldU8(SField const& field, unsigned char v)
{
@@ -827,6 +914,12 @@ STObject::setFieldObject(SField const& field, STObject const& v)
setFieldUsingAssignment(field, v);
}
void
STObject::setFieldJson(SField const& field, STJson const& v)
{
setFieldUsingAssignment(field, v);
}
Json::Value
STObject::getJson(JsonOptions options) const
{

View File

@@ -16,6 +16,8 @@
#include <xrpl/protocol/STBitString.h>
#include <xrpl/protocol/STBlob.h>
#include <xrpl/protocol/STCurrency.h>
#include <xrpl/protocol/STData.h>
#include <xrpl/protocol/STDataType.h>
#include <xrpl/protocol/STInteger.h>
#include <xrpl/protocol/STIssue.h>
#include <xrpl/protocol/STNumber.h>
@@ -903,6 +905,52 @@ parseLeaf(
}
break;
case STI_DATA: {
try
{
ret = detail::make_stvar<STData>(dataFromJson(field, value));
}
catch (std::exception const&)
{
std::cout << "STI_DATA failed for field: " << fieldName
<< " in object: " << json_name << "\n";
error = invalid_data(json_name, fieldName);
return ret;
}
break;
}
case STI_DATATYPE: {
try
{
ret = detail::make_stvar<STDataType>(dataTypeFromJson(field, value));
}
catch (std::exception const&)
{
std::cout << "STI_DATATYPE failed for field: " << fieldName
<< " in object: " << json_name << "\n";
error = invalid_data(json_name, fieldName);
return ret;
}
break;
}
case STI_JSON:
Throw<std::runtime_error>("STI_JSON is not supported");
// try
// {
// ret = detail::make_stvar<STDataType>(
// dataTypeFromJson(field, value));
// }
// catch (std::exception const&)
// {
// std::cout << "STI_DATATYPE failed for field: " << fieldName
// << " in object: " << json_name << "\n";
// error = invalid_data(json_name, fieldName);
// return ret;
// }
break;
default:
error = bad_type(json_name, fieldName);
return ret;

View File

@@ -8,8 +8,11 @@
#include <xrpl/protocol/STBitString.h>
#include <xrpl/protocol/STBlob.h>
#include <xrpl/protocol/STCurrency.h>
#include <xrpl/protocol/STData.h>
#include <xrpl/protocol/STDataType.h>
#include <xrpl/protocol/STInteger.h>
#include <xrpl/protocol/STIssue.h>
#include <xrpl/protocol/STJson.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STPathSet.h>
@@ -222,6 +225,15 @@ STVar::constructST(SerializedTypeID id, int depth, Args&&... args)
case STI_CURRENCY:
construct<STCurrency>(std::forward<Args>(args)...);
return;
case STI_DATA:
construct<STData>(std::forward<Args>(args)...);
return;
case STI_DATATYPE:
construct<STDataType>(std::forward<Args>(args)...);
return;
case STI_JSON:
construct<STJson>(std::forward<Args>(args)...);
return;
default:
Throw<std::runtime_error>("Unknown object type");
}

View File

@@ -106,6 +106,8 @@ transResults()
MAKE_ERROR(tecLIMIT_EXCEEDED, "Limit exceeded."),
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
MAKE_ERROR(tecWASM_REJECTED, "The custom WASM code that was run rejected your transaction."),
MAKE_ERROR(tecINVALID_PARAMETERS, "Contract parameters do not match the expected ABI."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
@@ -129,6 +131,8 @@ transResults()
MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."),
MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."),
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
MAKE_ERROR(tefNO_WASM, "There is no WASM code to run, but a WASM-specific field was included."),
MAKE_ERROR(tefWASM_FIELD_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."),
@@ -199,6 +203,7 @@ 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_WASM, "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

@@ -40,7 +40,7 @@ TxFormats::TxFormats()
#undef TRANSACTION
#define UNWRAP(...) __VA_ARGS__
#define TRANSACTION(tag, value, name, delegable, amendment, privileges, fields) \
#define TRANSACTION(tag, value, name, delegatable, amendment, privileges, emitable, fields) \
add(jss::name, tag, UNWRAP fields, getCommonFields());
#include <xrpl/protocol/detail/transactions.macro>

View File

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

View File

@@ -32,6 +32,7 @@ InfoSub::~InfoSub()
m_source.unsubValidations(mSeq);
m_source.unsubPeerStatus(mSeq);
m_source.unsubConsensus(mSeq);
m_source.unsubContractEvent(mSeq);
// Use the internal unsubscribe so that it won't call
// back to us and modify its own parameter

View File

@@ -28,19 +28,33 @@ ApplyContext::ApplyContext(
XRPL_ASSERT(
parentBatchId.has_value() == ((flags_ & tapBATCH) == tapBATCH),
"Parent Batch ID should be set if batch apply flag is set");
view_.emplace(&base_, flags_);
view_.emplace(&base_.view(), flags_);
}
void
ApplyContext::discard()
{
view_.emplace(&base_, flags_);
base_.discard();
view_.emplace(&base_.view(), flags_);
}
void
ApplyContext::finalize()
{
base_.commit();
view_.emplace(&base_.view(), flags_);
}
std::optional<TxMeta>
ApplyContext::apply(TER ter)
{
return view_->apply(base_, tx, ter, parentBatchId_, (flags_ & tapDRY_RUN) != 0u, journal);
if (wasmReturnCode_.has_value())
{
view_->setWasmReturnCode(*wasmReturnCode_);
}
view_->setGasUsed(gasUsed_);
return view_->apply(
base_.view(), tx, ter, parentBatchId_, (flags_ & tapDRY_RUN) != 0u, journal);
}
std::size_t
@@ -57,7 +71,7 @@ ApplyContext::visit(
std::shared_ptr<SLE const> const&,
std::shared_ptr<SLE const> const&)> const& func)
{
view_->visit(base_, func);
view_->visit(base_.view(), func);
}
TER

View File

@@ -4,6 +4,7 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/NFTokenUtils.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/protocol/Feature.h>
@@ -17,7 +18,6 @@
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/apply.h>
#include <xrpl/tx/transactors/delegate/DelegateUtils.h>
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
namespace xrpl {
@@ -582,6 +582,32 @@ Transactor::ticketDelete(
return tesSUCCESS;
}
std::pair<TER, XRPAmount>
Transactor::checkInvariants(TER result, XRPAmount fee)
{
// Check invariants: if `tecINVARIANT_FAILED` is not returned, we can
// proceed to apply the tx
result = ctx_.checkInvariants(result, fee);
if (result == tecINVARIANT_FAILED)
{
// if invariants checking failed again, reset the context and
// attempt to only claim a fee.
auto const resetResult = reset(fee);
if (!isTesSuccess(resetResult.first))
result = resetResult.first;
fee = resetResult.second;
// Check invariants again to ensure the fee claiming doesn't
// violate invariants.
if (isTesSuccess(result) || isTecClaim(result))
result = ctx_.checkInvariants(result, fee);
}
return {result, fee};
}
// check stuff before you bother to lock the ledger
void
Transactor::preCompute()
@@ -647,7 +673,8 @@ Transactor::checkSign(
}
auto const pkSigner = sigObject.getFieldVL(sfSigningPubKey);
// Ignore signature check on batch inner transactions
// Ignore signature check on batch inner transactions (e.g., emitted
// transactions from contracts)
if (parentBatchId && view.rules().enabled(featureBatch))
{
// Defensive Check: These values are also checked in Batch::preflight
@@ -983,6 +1010,22 @@ removeExpiredCredentials(ApplyView& view, std::vector<uint256> const& creds, bea
}
}
static void
modifyWasmDataFields(
ApplyView& view,
std::vector<std::pair<uint256, Blob>> const& wasmObjects,
beast::Journal viewJ)
{
for (auto const& [index, data] : wasmObjects)
{
if (auto const sle = view.peek(keylet::escrow(index)))
{
sle->setFieldVL(sfData, data);
view.update(sle);
}
}
}
static void
removeDeletedTrustLines(
ApplyView& view,
@@ -1030,8 +1073,10 @@ Transactor::reset(XRPAmount fee)
auto const balance = payerSle->getFieldAmount(sfBalance).xrp();
// balance should have already been checked in checkFee / preFlight.
// For batch/inner transactions (fee == 0), the balance can
// legitimately be zero (e.g. contract pseudo-accounts).
XRPL_ASSERT(
balance != beast::zero && (!view().open() || balance >= fee),
(fee == beast::zero || balance != beast::zero) && (!view().open() || balance >= fee),
"xrpl::Transactor::reset : valid balance");
// We retry/reject the transaction if the account balance is zero or
@@ -1134,7 +1179,8 @@ Transactor::operator()()
}
else if (
(result == tecOVERSIZE) || (result == tecKILLED) || (result == tecINCOMPLETE) ||
(result == tecEXPIRED) || (isTecClaimHardFail(result, view().flags())))
(result == tecEXPIRED) || (result == tecWASM_REJECTED) ||
(isTecClaimHardFail(result, view().flags())))
{
JLOG(j_.trace()) << "reapplying because of " << transToken(result);
@@ -1146,12 +1192,14 @@ Transactor::operator()()
std::vector<uint256> removedTrustLines;
std::vector<uint256> expiredNFTokenOffers;
std::vector<uint256> expiredCredentials;
std::vector<std::pair<uint256, Blob>> modifiedWasmObjects;
bool const doOffers = ((result == tecOVERSIZE) || (result == tecKILLED));
bool const doLines = (result == tecINCOMPLETE);
bool const doNFTokenOffers = (result == tecEXPIRED);
bool const doCredentials = (result == tecEXPIRED);
if (doOffers || doLines || doNFTokenOffers || doCredentials)
bool const doWasmData = (result == tecWASM_REJECTED);
if (doOffers || doLines || doNFTokenOffers || doCredentials || doWasmData)
{
ctx_.visit([doOffers,
&removedOffers,
@@ -1160,7 +1208,9 @@ Transactor::operator()()
doNFTokenOffers,
&expiredNFTokenOffers,
doCredentials,
&expiredCredentials](
&expiredCredentials,
doWasmData,
&modifiedWasmObjects](
uint256 const& index,
bool isDelete,
std::shared_ptr<SLE const> const& before,
@@ -1191,6 +1241,11 @@ Transactor::operator()()
if (doCredentials && before && after && (before->getType() == ltCREDENTIAL))
expiredCredentials.push_back(index);
}
if (doWasmData && before && after && (before->getType() == ltESCROW))
{
modifiedWasmObjects.push_back(std::make_pair(index, after->getFieldVL(sfData)));
}
});
}
@@ -1227,31 +1282,18 @@ Transactor::operator()()
view(), expiredCredentials, ctx_.registry.get().getJournal("View"));
}
if (result == tecWASM_REJECTED)
modifyWasmDataFields(
view(), modifiedWasmObjects, ctx_.registry.get().getJournal("View"));
applied = isTecClaim(result);
}
if (applied)
{
// Check invariants: if `tecINVARIANT_FAILED` is not returned, we can
// proceed to apply the tx
result = ctx_.checkInvariants(result, fee);
if (result == tecINVARIANT_FAILED)
{
// if invariants checking failed again, reset the context and
// attempt to only claim a fee.
auto const resetResult = reset(fee);
if (!isTesSuccess(resetResult.first))
result = resetResult.first;
fee = resetResult.second;
// Check invariants again to ensure the fee claiming doesn't
// violate invariants.
if (isTesSuccess(result) || isTecClaim(result))
result = ctx_.checkInvariants(result, fee);
}
auto const invariantsResult = checkInvariants(result, fee);
result = invariantsResult.first;
fee = invariantsResult.second;
// We ran through the invariant checker, which can, in some cases,
// return a tef error code. Don't apply the transaction in that case.
if (!isTecClaim(result) && !isTesSuccess(result))
@@ -1286,6 +1328,70 @@ Transactor::operator()()
applied = false;
}
if (metadata && ctx_.getEmittedTxns().size() > 0)
{
OpenView emittedTxnsView(batch_view, ctx_.openView());
auto const parentBatchId = ctx_.tx.getTransactionID();
auto applyOneTransaction = [this, &parentBatchId, &emittedTxnsView](STTx const& tx) {
OpenView perTxBatchView(batch_view, emittedTxnsView);
auto const ret = xrpl::apply(
ctx_.registry, perTxBatchView, parentBatchId, tx, tapBATCH, ctx_.journal);
XRPL_ASSERT(
ret.applied == (isTesSuccess(ret.ter) || isTecClaim(ret.ter)),
"Inner transaction should not be applied");
JLOG(ctx_.journal.debug())
<< "BatchTrace[" << parentBatchId << "]: " << tx.getTransactionID() << " "
<< (ret.applied ? "applied" : "failure") << ": " << transToken(ret.ter);
// If the transaction should be applied push its changes to the
// whole-batch view.
if (ret.applied && (isTesSuccess(ret.ter) || isTecClaim(ret.ter)))
perTxBatchView.apply(emittedTxnsView);
return ret;
};
bool emitResult = true;
auto emittedTxns = ctx_.getEmittedTxns();
while (!emittedTxns.empty())
{
auto txn = emittedTxns.front();
emittedTxns.pop();
auto const result = applyOneTransaction(*txn);
XRPL_ASSERT(
result.applied == (isTesSuccess(result.ter) || isTecClaim(result.ter)),
"Outer Batch failure, inner transaction should not be applied");
if (!isTesSuccess(result.ter))
emitResult = false;
}
if (emitResult)
emittedTxnsView.apply(ctx_.openView());
else
{
// reset context
result = tecWASM_REJECTED;
auto const resetResult = reset(fee);
if (!isTesSuccess(resetResult.first))
result = resetResult.first;
fee = resetResult.second;
// InvariantCheck
auto const invariantsResult = checkInvariants(result, fee);
result = invariantsResult.first;
fee = invariantsResult.second;
// apply
metadata = ctx_.apply(result);
}
}
ctx_.finalize();
JLOG(j_.trace()) << (applied ? "applied " : "not applied ") << transToken(result);
return {result, applied, metadata};

View File

@@ -859,7 +859,8 @@ ValidPseudoAccounts::visitEntry(
errors_.emplace_back(error.str());
}
}
if (before && before->at(sfSequence) != after->at(sfSequence))
if (before && before->at(sfSequence) != after->at(sfSequence) &&
!after->isFieldPresent(sfContractID))
{
errors_.emplace_back("pseudo-account sequence changed");
}

View File

@@ -2,11 +2,12 @@
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/helpers/NFTokenUtils.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/nftPageMask.h>
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
namespace xrpl {

View File

@@ -0,0 +1,382 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/NFTokenUtils.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/digest.h>
#include <xrpl/tx/transactors/DeleteUtils.h>
#include <xrpl/tx/transactors/account/SignerListSet.h>
#include <xrpl/tx/transactors/contract/ContractDelete.h>
#include <xrpl/tx/transactors/delegate/DelegateSet.h>
#include <xrpl/tx/transactors/did/DIDDelete.h>
#include <xrpl/tx/transactors/oracle/OracleDelete.h>
#include <xrpl/tx/transactors/payment/DepositPreauth.h>
#include <unordered_set>
namespace xrpl {
// Local function definitions that provides signature compatibility.
TER
offerDelete(
ServiceRegistry& registry,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return offerDelete(view, sleDel, j);
}
TER
removeSignersFromLedger(
ServiceRegistry& registry,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return SignerListSet::removeFromLedger(registry, view, account, j);
}
TER
removeTicketFromLedger(
ServiceRegistry&,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const&,
beast::Journal j)
{
return Transactor::ticketDelete(view, account, delIndex, j);
}
TER
removeDepositPreauthFromLedger(
ServiceRegistry&,
ApplyView& view,
AccountID const&,
uint256 const& delIndex,
std::shared_ptr<SLE> const&,
beast::Journal j)
{
return DepositPreauth::removeFromLedger(view, delIndex, j);
}
TER
removeNFTokenOfferFromLedger(
ServiceRegistry& registry,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal)
{
if (!nft::deleteTokenOffer(view, sleDel))
return tefBAD_LEDGER;
return tesSUCCESS;
}
TER
removeDIDFromLedger(
ServiceRegistry& registry,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return DIDDelete::deleteSLE(view, sleDel, account, j);
}
TER
removeOracleFromLedger(
ServiceRegistry&,
ApplyView& view,
AccountID const& account,
uint256 const&,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return OracleDelete::deleteOracle(view, sleDel, account, j);
}
TER
removeCredentialFromLedger(
ServiceRegistry&,
ApplyView& view,
AccountID const&,
uint256 const&,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return credentials::deleteSLE(view, sleDel, j);
}
TER
removeDelegateFromLedger(
ServiceRegistry& registry,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return DelegateSet::deleteDelegate(view, sleDel, account, j);
}
TER
removeContractFromLedger(
ServiceRegistry& registry,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return ContractDelete::deleteContract(view, sleDel, account, j);
}
// Return nullptr if the LedgerEntryType represents an obligation that can't
// be deleted. Otherwise return the pointer to the function that can delete
// the non-obligation
DeleterFuncPtr
nonObligationDeleter(LedgerEntryType t)
{
switch (t)
{
case ltOFFER:
return offerDelete;
case ltSIGNER_LIST:
return removeSignersFromLedger;
case ltTICKET:
return removeTicketFromLedger;
case ltDEPOSIT_PREAUTH:
return removeDepositPreauthFromLedger;
case ltNFTOKEN_OFFER:
return removeNFTokenOfferFromLedger;
case ltDID:
return removeDIDFromLedger;
case ltORACLE:
return removeOracleFromLedger;
case ltCREDENTIAL:
return removeCredentialFromLedger;
case ltDELEGATE:
return removeDelegateFromLedger;
case ltCONTRACT:
return removeContractFromLedger;
default:
return nullptr;
}
}
TER
deletePreclaim(
PreclaimContext const& ctx,
std::uint32_t seqDelta,
AccountID const account,
AccountID const dest,
bool isPseudoAccount)
{
auto destSle = ctx.view.read(keylet::account(dest));
if (!destSle)
return tecNO_DST;
if ((*destSle)[sfFlags] & lsfRequireDestTag && !ctx.tx[~sfDestinationTag])
return tecDST_TAG_NEEDED;
// If credentials are provided - check them anyway
if (auto const err = credentials::valid(ctx.tx, ctx.view, account, ctx.j); !isTesSuccess(err))
return err;
// if credentials then postpone auth check to doApply, to check for expired
// credentials
if (!ctx.tx.isFieldPresent(sfCredentialIDs))
{
// Check whether the destination account requires deposit authorization.
if (destSle->getFlags() & lsfDepositAuth)
{
if (!ctx.view.exists(keylet::depositPreauth(dest, account)) && !isPseudoAccount)
return tecNO_PERMISSION;
}
}
auto srcSle = ctx.view.read(keylet::account(account));
XRPL_ASSERT(srcSle, "xrpl::DeleteAccount::preclaim : non-null account");
if (!srcSle)
return terNO_ACCOUNT;
{
// If an issuer has any issued NFTs resident in the ledger then it
// cannot be deleted.
if ((*srcSle)[~sfMintedNFTokens] != (*srcSle)[~sfBurnedNFTokens])
return tecHAS_OBLIGATIONS;
// If the account owns any NFTs it cannot be deleted.
Keylet const first = keylet::nftpage_min(account);
Keylet const last = keylet::nftpage_max(account);
auto const cp = ctx.view.read(
Keylet(ltNFTOKEN_PAGE, ctx.view.succ(first.key, last.key.next()).value_or(last.key)));
if (cp)
return tecHAS_OBLIGATIONS;
}
// We don't allow an account to be deleted if its sequence number
// is within 256 of the current ledger. This prevents replay of old
// transactions if this account is resurrected after it is deleted.
//
// We look at the account's Sequence rather than the transaction's
// Sequence in preparation for Tickets.
if ((*srcSle)[sfSequence] + seqDelta > ctx.view.seq())
return tecTOO_SOON;
// When fixNFTokenRemint is enabled, we don't allow an account to be
// deleted if <FirstNFTokenSequence + MintedNFTokens> is within 256 of the
// current ledger. This is to prevent having duplicate NFTokenIDs after
// account re-creation.
//
// Without this restriction, duplicate NFTokenIDs can be reproduced when
// authorized minting is involved. Because when the minter mints a NFToken,
// the issuer's sequence does not change. So when the issuer re-creates
// their account and mints a NFToken, it is possible that the
// NFTokenSequence of this NFToken is the same as the one that the
// authorized minter minted in a previous ledger.
if ((*srcSle)[~sfFirstNFTokenSequence].value_or(0) + (*srcSle)[~sfMintedNFTokens].value_or(0) +
seqDelta >
ctx.view.seq())
return tecTOO_SOON;
// Verify that the account does not own any objects that would prevent
// the account from being deleted.
Keylet const ownerDirKeylet{keylet::ownerDir(account)};
if (dirIsEmpty(ctx.view, ownerDirKeylet))
return tesSUCCESS;
std::shared_ptr<SLE const> sleDirNode{};
unsigned int uDirEntry{0};
uint256 dirEntry{beast::zero};
// Account has no directory at all. This _should_ have been caught
// by the dirIsEmpty() check earlier, but it's okay to catch it here.
if (!cdirFirst(ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
return tesSUCCESS;
std::int32_t deletableDirEntryCount{0};
do
{
// Make sure any directory node types that we find are the kind
// we can delete.
auto sleItem = ctx.view.read(keylet::child(dirEntry));
if (!sleItem)
{
// Directory node has an invalid index. Bail out.
JLOG(ctx.j.fatal()) << "DeleteAccount: directory node in ledger " << ctx.view.seq()
<< " has index to object that is missing: " << to_string(dirEntry);
return tefBAD_LEDGER;
}
LedgerEntryType const nodeType{safe_cast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
if (!nonObligationDeleter(nodeType))
return tecHAS_OBLIGATIONS;
// We found a deletable directory entry. Count it. If we find too
// many deletable directory entries then bail out.
if (++deletableDirEntryCount > maxDeletableDirEntries)
return tefTOO_BIG;
} while (cdirNext(ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
return tesSUCCESS;
}
TER
deleteDoApply(
ApplyContext& applyCtx,
STAmount const& accountBalance,
AccountID const& account,
AccountID const& dest)
{
auto& view = applyCtx.view();
STTx const tx = applyCtx.tx;
beast::Journal j = applyCtx.journal;
auto srcSle = view.peek(keylet::account(account));
XRPL_ASSERT(srcSle, "xrpl::deleteDoApply : non-null source account");
if (!srcSle)
return tefBAD_LEDGER;
auto destSle = view.peek(keylet::account(dest));
XRPL_ASSERT(destSle, "xrpl::deleteDoApply : non-null destination account");
if (!destSle)
return tefBAD_LEDGER;
if (tx.isFieldPresent(sfCredentialIDs))
{
if (auto err = verifyDepositPreauth(tx, view, account, dest, destSle, j);
!isTesSuccess(err))
return err;
}
Keylet const ownerDirKeylet{keylet::ownerDir(account)};
auto const ter = cleanupOnAccountDelete(
view,
ownerDirKeylet,
[&](LedgerEntryType nodeType,
uint256 const& dirEntry,
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
if (auto deleter = nonObligationDeleter(nodeType))
{
TER const result{deleter(applyCtx.registry, view, account, dirEntry, sleItem, j)};
return {result, SkipEntry::No};
}
UNREACHABLE(
"xrpl::deleteDoApply : undeletable item not found "
"in preclaim");
JLOG(j.error()) << "DeleteAccount undeletable item not "
"found in preclaim.";
return {tecHAS_OBLIGATIONS, SkipEntry::No};
},
j);
if (ter != tesSUCCESS)
return ter;
// Transfer any XRP remaining after the fee is paid to the destination:
(*destSle)[sfBalance] = (*destSle)[sfBalance] + accountBalance;
(*srcSle)[sfBalance] = (*srcSle)[sfBalance] - accountBalance;
applyCtx.deliver(accountBalance);
// DA: Pseudo accounts can have 0 balance, so we skip this assert.
// FIX FIX FIX: DA FIX
// XRPL_ASSERT(
// (*srcSle)[sfBalance] == XRPAmount(0),
// "xrpl::deleteDoApply : source balance is zero");
// If there's still an owner directory associated with the source account
// delete it.
if (view.exists(ownerDirKeylet) && !view.emptyDirDelete(ownerDirKeylet))
{
JLOG(j.error()) << "DeleteAccount cannot delete root dir node of " << toBase58(account);
return tecHAS_OBLIGATIONS;
}
// Re-arm the password change fee if we can and need to.
if (accountBalance > XRPAmount(0) && (*destSle).isFlag(lsfPasswordSpent))
(*destSle).clearFlag(lsfPasswordSpent);
view.update(destSle);
view.erase(srcSle);
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -3,6 +3,7 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/NFTokenUtils.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -11,9 +12,9 @@
#include <xrpl/protocol/Units.h>
#include <xrpl/tx/transactors/account/AccountDelete.h>
#include <xrpl/tx/transactors/account/SignerListSet.h>
#include <xrpl/tx/transactors/contract/ContractDelete.h>
#include <xrpl/tx/transactors/delegate/DelegateSet.h>
#include <xrpl/tx/transactors/did/DIDDelete.h>
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
#include <xrpl/tx/transactors/oracle/OracleDelete.h>
#include <xrpl/tx/transactors/payment/DepositPreauth.h>
@@ -169,6 +170,18 @@ removeDelegateFromLedger(
return DelegateSet::deleteDelegate(view, sleDel, account, j);
}
TER
removeContractFromLedger(
ServiceRegistry&,
ApplyView& view,
AccountID const& account,
uint256 const& /*delIndex*/,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return ContractDelete::deleteContract(view, sleDel, account, j);
}
// Return nullptr if the LedgerEntryType represents an obligation that can't
// be deleted. Otherwise return the pointer to the function that can delete
// the non-obligation
@@ -195,6 +208,8 @@ nonObligationDeleter(LedgerEntryType t)
return removeCredentialFromLedger;
case ltDELEGATE:
return removeDelegateFromLedger;
case ltCONTRACT:
return removeContractFromLedger;
default:
return nullptr;
}

View File

@@ -0,0 +1,329 @@
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/detail/ApplyViewBase.h>
#include <xrpl/ledger/helpers/ContractUtils.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/apply.h>
#include <xrpl/tx/transactors/contract/ContractCall.h>
#include <xrpl/tx/wasm/ContractHostFuncImpl.h>
#include <xrpl/tx/wasm/WasmVM.h>
namespace xrpl {
XRPAmount
ContractCall::calculateBaseFee(ReadView const& view, STTx const& tx)
{
XRPAmount extraFee{0};
if (auto const allowance = tx[~sfComputationAllowance]; allowance)
{
extraFee += (*allowance) * view.fees().gasPrice / MICRO_DROPS_PER_DROP;
}
return Transactor::calculateBaseFee(view, tx) + extraFee;
}
NotTEC
ContractCall::preflight(PreflightContext const& ctx)
{
auto const flags = ctx.tx.getFlags();
if (flags & tfUniversalMask)
{
JLOG(ctx.j.trace()) << "ContractCreate: tfUniversalMask is not allowed.";
return temINVALID_FLAG;
}
return tesSUCCESS;
}
TER
ContractCall::preclaim(PreclaimContext const& ctx)
{
AccountID const account = ctx.tx[sfAccount];
auto const accountSle = ctx.view.read(keylet::account(account));
if (!accountSle)
{
JLOG(ctx.j.trace()) << "ContractCall: Account does not exist.";
return tecNO_TARGET;
}
// The ContractAccount doesn't exist or isn't a smart contract
// pseudo-account.
AccountID const contractAccount = ctx.tx[sfContractAccount];
auto const caSle = ctx.view.read(keylet::account(contractAccount));
if (!caSle)
{
JLOG(ctx.j.trace()) << "ContractCall: Contract Account does not exist.";
return tecNO_TARGET;
}
// The function doesn't exist on the provided contract.
uint256 const contractID = caSle->getFieldH256(sfContractID);
auto const contractSle = ctx.view.read(keylet::contract(contractID));
if (!contractSle)
{
JLOG(ctx.j.trace()) << "ContractCall: Contract does not exist.";
return tecNO_TARGET;
}
if (!contractSle->at(sfContractHash))
{
JLOG(ctx.j.trace()) << "ContractCall: Contract does not have a hash.";
return tecNO_TARGET;
}
auto const contractSourceSle =
ctx.view.read(keylet::contractSource(contractSle->at(sfContractHash)));
if (!contractSourceSle)
{
JLOG(ctx.j.trace()) << "ContractCall: ContractSource does not exist.";
return tecNO_TARGET;
}
if (!contractSourceSle->isFieldPresent(sfFunctions))
{
JLOG(ctx.j.trace()) << "ContractCall: Contract does not have any functions defined.";
return temMALFORMED;
}
auto const& functions = contractSourceSle->getFieldArray(sfFunctions);
auto const functionName = ctx.tx.getFieldVL(sfFunctionName);
std::string functionNameHexStr(functionName.begin(), functionName.end());
auto it = std::find_if(
functions.begin(), functions.end(), [&functionNameHexStr](STObject const& func) {
auto const funcName = func.getFieldVL(sfFunctionName);
std::string functionNameDefHexStr(funcName.begin(), funcName.end());
return functionNameDefHexStr == functionNameHexStr;
});
if (it == functions.end())
{
JLOG(ctx.j.trace()) << "ContractCall: FunctionName: " << functionNameHexStr
<< " does not exist in contract abi.";
return temMALFORMED;
}
if (ctx.tx.isFieldPresent(sfParameters))
{
STArray const& params = ctx.tx.getFieldArray(sfParameters);
if (auto ter =
contract::preclaimFlagParameters(ctx.view, account, contractAccount, params, ctx.j);
!isTesSuccess(ter))
{
JLOG(ctx.j.trace()) << "ContractCreate: Failed to preclaim flag parameters.";
return ter;
}
}
// The parameters don't match the function's ABI.
return tesSUCCESS;
}
TER
ContractCall::doApply()
{
AccountID const contractAccount = ctx_.tx[sfContractAccount];
auto const caSle = ctx_.view().read(keylet::account(contractAccount));
if (!caSle)
{
JLOG(j_.trace()) << "ContractCall: ContractAccount does not exist.";
return tefINTERNAL;
}
auto const accountSle = ctx_.view().read(keylet::account(account_));
if (!accountSle)
{
JLOG(j_.trace()) << "ContractCall: Account does not exist.";
return tefINTERNAL;
}
uint256 const contractID = caSle->getFieldH256(sfContractID);
Keylet const k = keylet::contract(contractID);
auto const contractSle = ctx_.view().read(k);
if (!contractSle)
{
JLOG(j_.trace()) << "ContractCall: Contract does not exist.";
return tefINTERNAL;
}
uint256 const contractHash = contractSle->at(sfContractHash);
auto const contractSourceSle =
ctx_.view().read(keylet::contractSource(contractSle->at(sfContractHash)));
if (!contractSourceSle)
{
JLOG(j_.trace()) << "ContractCall: ContractSource does not exist.";
return tefINTERNAL;
}
// // Handle the flags for the contract call.
if (ctx_.tx.isFieldPresent(sfParameters))
{
STArray const& params = ctx_.tx.getFieldArray(sfParameters);
if (auto ter = contract::doApplyFlagParameters(
ctx_.view(),
ctx_.tx,
account_,
contractAccount,
params,
preFeeBalance_,
ctx_.journal);
!isTesSuccess(ter))
{
JLOG(ctx_.journal.trace()) << "ContractCall: Failed to handle flag parameters.";
return ter;
}
}
// WASM execution
auto const wasmStr = contractSourceSle->getFieldVL(sfContractCode);
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
auto const functionName = ctx_.tx.getFieldVL(sfFunctionName);
std::string funcName(functionName.begin(), functionName.end());
auto const contractFunctions = contractSle->isFieldPresent(sfFunctions)
? contractSle->getFieldArray(sfFunctions)
: contractSourceSle->getFieldArray(sfFunctions);
std::optional<STObject> function;
for (auto const& contractFunction : contractFunctions)
{
if (contractFunction.getFieldVL(sfFunctionName) == functionName)
function = contractFunction;
}
if (!function)
{
JLOG(j_.trace()) << "ContractCall: FunctionName does not exist in contract.";
return tefINTERNAL;
}
// ContractCall Parameters
std::vector<xrpl::ParameterValueVec> functionParameters;
if (ctx_.tx.isFieldPresent(sfParameters))
{
STArray const& funcParams = ctx_.tx.getFieldArray(sfParameters);
functionParameters = getParameterValueVec(funcParams);
}
// ContractSource/Contract Default Parameters
std::vector<xrpl::ParameterValueVec> instanceParameters;
if (contractSle->isFieldPresent(sfInstanceParameterValues))
{
STArray const& instParams = contractSle->getFieldArray(sfInstanceParameterValues);
instanceParameters = getParameterValueVec(instParams);
}
// The parameters don't match the function's ABI.
std::vector<ParameterTypeVec> typeVec;
if (function->isFieldPresent(sfParameters))
{
STArray const& funcParamsDef = function->getFieldArray(sfParameters);
typeVec = xrpl::getParameterTypeVec(funcParamsDef);
if (functionParameters.size() != typeVec.size())
return tecINVALID_PARAMETERS;
}
for (std::size_t i = 0; i < functionParameters.size(); i++)
{
if (functionParameters[i].value.getInnerSType() != typeVec[i].type.getInnerSType())
return tecINVALID_PARAMETERS;
}
xrpl::ContractDataMap dataMap;
xrpl::ContractEventMap eventMap;
ContractContext contractCtx = {
.applyCtx = ctx_,
.instanceParameters = instanceParameters,
.functionParameters = functionParameters,
.built_txns = {},
.expected_etxn_count = 1,
.generation = 0,
.burden = 0,
.result =
{
.contractHash = contractHash,
.contractKeylet = k,
.contractSourceKeylet = k,
.contractAccountKeylet = k,
.contractAccount = contractAccount,
.nextSequence = caSle->getFieldU32(sfSequence),
.otxnAccount = account_,
.otxnId = ctx_.tx.getTransactionID(),
.exitReason = "",
.exitCode = -1,
.dataMap = dataMap,
.eventMap = eventMap,
.changedDataCount = 0,
},
};
ContractHostFunctionsImpl ledgerDataProvider(contractCtx);
if (!ctx_.tx.isFieldPresent(sfComputationAllowance))
{
JLOG(j_.trace()) << "ContractCall: Computation allowance is not set.";
return tefINTERNAL;
}
std::uint32_t allowance = ctx_.tx[sfComputationAllowance];
auto re = runEscrowWasm(wasm, ledgerDataProvider, allowance, funcName, {});
// Wasm Result
if (re.has_value())
{
// TODO: better error handling for this conversion
// if (allowance > re.value().cost)
// {
// allowance -= static_cast<std::uint32_t>(re.value().cost);
// // auto const returnAllowance = [&]() {
// // ctx_.view().update(
// // keylet::account(contractAccount),
// // [allowance](SLE& sle) {
// // sle.setFieldU32(
// // sfBalance,
// // sle.getFieldU32(sfBalance) + allowance);
// // });
// // };
// // returnAllowance();
// }
ctx_.setGasUsed(static_cast<uint32_t>(re.value().cost));
auto ret = re.value().result;
if (ret < 0)
{
JLOG(j_.trace()) << "WASM Execution Failed: " << contractCtx.result.exitReason;
ctx_.setWasmReturnCode(ret);
// ctx_.setWasmReturnStr(contractCtx.result.exitReason);
return tecWASM_REJECTED;
}
if (auto res = contract::finalizeContractData(
ctx_.registry,
ctx_.view(),
contractAccount,
contractCtx.result.dataMap,
contractCtx.result.eventMap,
ctx_.tx.getTransactionID());
!isTesSuccess(res))
{
JLOG(j_.trace()) << "Contract data finalization failed: " << transHuman(res);
return res;
}
ctx_.setWasmReturnCode(ret);
// ctx_.setWasmReturnStr(contractCtx.result.exitReason);
ctx_.setEmittedTxns(contractCtx.result.emittedTxns);
return tesSUCCESS;
}
else
{
JLOG(j_.trace()) << "WASM Failure: " + transHuman(re.error());
auto const errorCode = TERtoInt(re.error());
ctx_.setWasmReturnCode(errorCode);
// ctx_.setWasmReturnStr(contractCtx.result.exitReason);
return re.error();
}
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -0,0 +1,32 @@
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/transactors/contract/ContractClawback.h>
namespace xrpl {
NotTEC
ContractClawback::preflight(PreflightContext const& ctx)
{
auto const flags = ctx.tx.getFlags();
if (flags & tfUniversalMask)
{
JLOG(ctx.j.trace()) << "ContractClawback: tfUniversalMask is not allowed.";
return temINVALID_FLAG;
}
return tesSUCCESS;
}
TER
ContractClawback::preclaim(PreclaimContext const& ctx)
{
return tesSUCCESS;
}
TER
ContractClawback::doApply()
{
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -0,0 +1,272 @@
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/ContractUtils.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STData.h>
#include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/digest.h>
#include <xrpl/tx/transactors/contract/ContractCreate.h>
namespace xrpl {
XRPAmount
ContractCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
{
XRPAmount const maxAmount{std::numeric_limits<XRPAmount::value_type>::max()};
XRPAmount createFee{0};
if (tx.isFieldPresent(sfCreateCode))
createFee = XRPAmount{contract::contractCreateFee(tx.getFieldVL(sfCreateCode).size())};
if (createFee > maxAmount - view.fees().increment)
{
JLOG(debugLog().trace()) << "ContractCreate: Create fee overflow detected.";
return XRPAmount{INITIAL_XRP};
}
createFee += view.fees().increment;
auto baseFee = Transactor::calculateBaseFee(view, tx);
if (baseFee > maxAmount - createFee)
{
JLOG(debugLog().trace()) << "ContractCreate: Total fee overflow detected.";
return XRPAmount{INITIAL_XRP};
}
return createFee + baseFee;
}
std::uint32_t
ContractCreate::getFlagsMask(PreflightContext const& ctx)
{
return tfContractMask;
}
NotTEC
ContractCreate::preflight(PreflightContext const& ctx)
{
auto const flags = ctx.tx.getFlags();
if ((flags & (tfCodeImmutable | tfABIImmutable | tfImmutable)) > tfImmutable)
{
JLOG(ctx.j.trace()) << "ContractCreate: Cannot set more than one immutability flag.";
return temINVALID_FLAG;
}
if (!ctx.tx.isFieldPresent(sfContractCode) && !ctx.tx.isFieldPresent(sfContractHash))
{
JLOG(ctx.j.trace()) << "ContractCreate: Neither ContractCode nor ContractHash present";
return temMALFORMED;
}
if (ctx.tx.isFieldPresent(sfContractCode) && ctx.tx.isFieldPresent(sfContractHash))
{
JLOG(ctx.j.trace()) << "ContractCreate: Both ContractCode and ContractHash present";
return temMALFORMED;
}
if (auto const res = contract::preflightFunctions(ctx.tx, ctx.j); !isTesSuccess(res))
{
JLOG(ctx.j.trace()) << "ContractCreate: Functions validation failed: " << transToken(res);
return res;
}
if (auto const res = contract::preflightInstanceParameters(ctx.tx, ctx.j); !isTesSuccess(res))
{
JLOG(ctx.j.trace()) << "ContractCreate: InstanceParameters validation failed: "
<< transToken(res);
return res;
}
if (auto const res = contract::preflightInstanceParameterValues(ctx.tx, ctx.j);
!isTesSuccess(res))
{
JLOG(ctx.j.trace()) << "ContractCreate: InstanceParameterValues validation failed: "
<< transToken(res);
return res;
}
return tesSUCCESS;
}
TER
ContractCreate::preclaim(PreclaimContext const& ctx)
{
// ContractHash is provided but there is no existing corresponding
// ContractSource ledger object
bool isInstall = ctx.tx.isFieldPresent(sfContractHash);
auto contractHash = ctx.tx.at(~sfContractHash);
if (isInstall && !ctx.view.exists(keylet::contractSource(*contractHash)))
{
JLOG(ctx.j.trace()) << "ContractCreate: ContractHash provided but no "
"corresponding ContractSource exists";
return temMALFORMED;
}
// The ContractCode provided is invalid.
if (ctx.tx.isFieldPresent(sfContractCode))
{
xrpl::Blob wasmBytes = ctx.tx.getFieldVL(sfContractCode);
if (wasmBytes.empty())
{
JLOG(ctx.j.trace()) << "ContractCreate: ContractCode provided is empty.";
return temMALFORMED;
}
contractHash = xrpl::sha512Half_s(xrpl::Slice(wasmBytes.data(), wasmBytes.size()));
if (ctx.view.exists(keylet::contractSource(*contractHash)))
isInstall = true;
// Iterate through the functions and validate them?
// HostFunctions mock;
// auto const re = preflightEscrowWasm(wasmBytes, "finish", {}, &mock,
// ctx.j); if (!isTesSuccess(re))
// {
// JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM";
// return re;
// }
}
// The ABI provided in Functions doesn't match the code.
// InstanceParameters don't match what's in the existing ContractSource
// ledger object.
if (isInstall && ctx.tx.isFieldPresent(sfInstanceParameterValues))
{
auto const sle = ctx.view.read(keylet::contractSource(*contractHash));
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
// Already validated in preflight, but we can check here too.
auto const& instanceParams = sle->getFieldArray(sfInstanceParameters);
auto const& instanceParamValues = ctx.tx.getFieldArray(sfInstanceParameterValues);
if (auto const isValid =
contract::validateParameterMapping(instanceParams, instanceParamValues, ctx.j);
!isValid)
{
JLOG(ctx.j.trace()) << "ContractCreate: InstanceParameters do not match what's in "
"the existing ContractSource ledger object.";
return temMALFORMED;
}
}
return tesSUCCESS;
}
TER
ContractCreate::doApply()
{
auto const accountSle = ctx_.view().peek(keylet::account(account_));
if (!accountSle)
{
JLOG(j_.trace()) << "ContractCreate: Account not found.";
return tefINTERNAL; // LCOV_EXCL_LINE
}
std::shared_ptr<SLE> sourceSle;
bool isInstall = ctx_.tx.isFieldPresent(sfContractHash);
auto contractHash = ctx_.tx[~sfContractHash];
xrpl::Blob wasmBytes;
if (ctx_.tx.isFieldPresent(sfContractCode))
{
wasmBytes = ctx_.tx.getFieldVL(sfContractCode);
contractHash = xrpl::sha512Half_s(xrpl::Slice(wasmBytes.data(), wasmBytes.size()));
if (ctx_.view().exists(keylet::contractSource(*contractHash)))
isInstall = true;
}
if (isInstall)
{
sourceSle = ctx_.view().peek(keylet::contractSource(*contractHash));
if (!sourceSle)
return tefINTERNAL; // LCOV_EXCL_LINE
sourceSle->at(sfReferenceCount) = sourceSle->getFieldU64(sfReferenceCount) + 1;
ctx_.view().update(sourceSle);
}
else
{
sourceSle = std::make_shared<SLE>(keylet::contractSource(*contractHash));
sourceSle->at(sfContractHash) = *contractHash;
sourceSle->at(sfContractCode) = makeSlice(wasmBytes);
sourceSle->setFieldArray(sfFunctions, ctx_.tx.getFieldArray(sfFunctions));
if (ctx_.tx.isFieldPresent(sfInstanceParameters))
sourceSle->setFieldArray(
sfInstanceParameters, ctx_.tx.getFieldArray(sfInstanceParameters));
sourceSle->at(sfReferenceCount) = 1;
ctx_.view().insert(sourceSle);
}
std::uint32_t const seq = ctx_.tx.getSeqValue();
auto const contractKeylet = keylet::contract(*contractHash, account_, seq);
auto contractSle = std::make_shared<SLE>(contractKeylet);
auto maybePseudo = createPseudoAccount(view(), contractSle->key(), sfContractID);
if (!maybePseudo)
return maybePseudo.error(); // LCOV_EXCL_LINE
auto& pseudoSle = *maybePseudo;
auto pseudoAccount = pseudoSle->at(sfAccount);
contractSle->at(sfContractAccount) = pseudoAccount;
contractSle->at(sfOwner) = account_;
contractSle->at(sfFlags) = ctx_.tx.getFlags();
contractSle->at(sfSequence) = seq;
contractSle->at(sfContractHash) = *contractHash;
if (ctx_.tx.isFieldPresent(sfInstanceParameterValues))
contractSle->setFieldArray(
sfInstanceParameterValues, ctx_.tx.getFieldArray(sfInstanceParameterValues));
if (ctx_.tx.isFieldPresent(sfURI))
contractSle->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
ctx_.view().insert(contractSle);
// Handle the instance parameters for the contract creation.
if (ctx_.tx.isFieldPresent(sfInstanceParameterValues))
{
STArray const& params = ctx_.tx.getFieldArray(sfInstanceParameterValues);
// Note: We have to do preclaim and apply here because we will only have
// the pseudo account after the contract is created.
if (auto ter =
contract::preclaimFlagParameters(ctx_.view(), account_, pseudoAccount, params, j_);
!isTesSuccess(ter))
{
JLOG(j_.trace()) << "ContractCreate: Failed to preclaim flag parameters.";
return ter;
}
if (auto ter = contract::doApplyFlagParameters(
ctx_.view(), ctx_.tx, account_, pseudoAccount, params, preFeeBalance_, j_);
!isTesSuccess(ter))
{
JLOG(j_.trace()) << "ContractCreate: Failed to apply flag parameters.";
return ter;
}
}
// Add Contract to ContractAccount Dir
// TODO: use dirLink
{
auto const page = view().dirInsert(
keylet::ownerDir(pseudoAccount), contractKeylet, describeOwnerDir(pseudoAccount));
if (!page)
return tecDIR_FULL;
contractSle->setFieldU64(sfOwnerNode, *page);
adjustOwnerCount(ctx_.view(), pseudoSle, 1, j_);
}
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -0,0 +1,149 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/transactors/DeleteUtils.h>
#include <xrpl/tx/transactors/contract/ContractDelete.h>
namespace xrpl {
NotTEC
ContractDelete::preflight(PreflightContext const& ctx)
{
auto const flags = ctx.tx.getFlags();
if (flags & tfUniversalMask)
{
JLOG(ctx.j.trace()) << "ContractDelete: only tfUniversalMask is allowed.";
return temINVALID_FLAG;
}
return tesSUCCESS;
}
TER
ContractDelete::preclaim(PreclaimContext const& ctx)
{
AccountID const account = ctx.tx.getAccountID(sfAccount);
AccountID const contractAccount =
ctx.tx.isFieldPresent(sfContractAccount) ? ctx.tx.getAccountID(sfContractAccount) : account;
auto const caSle = ctx.view.read(keylet::account(contractAccount));
if (!caSle)
{
JLOG(ctx.j.trace()) << "ContractDelete: Account does not exist.";
return terNO_ACCOUNT;
}
if (!caSle->isFieldPresent(sfContractID))
{
JLOG(ctx.j.trace()) << "ContractDelete: Account is not a smart "
"contract pseudo-account.";
return tecNO_PERMISSION;
}
uint256 const contractID = caSle->getFieldH256(sfContractID);
auto const contractSle = ctx.view.read(keylet::contract(contractID));
if (!contractSle)
{
JLOG(ctx.j.trace()) << "ContractDelete: Contract does not exist.";
return tecNO_TARGET;
}
if (contractSle->getAccountID(sfOwner) != account)
{
JLOG(ctx.j.trace()) << "ContractDelete: Cannot delete a contract that "
"does not belong to the account.";
return tecNO_PERMISSION;
}
std::uint32_t flags = contractSle->getFlags();
// Check if the contract is undeletable.
if (flags & tfUndeletable)
{
JLOG(ctx.j.trace()) << "ContractDelete: Contract is undeletable.";
return tecNO_PERMISSION;
}
AccountID const owner = contractSle->getAccountID(sfOwner);
if (auto const res = deletePreclaim(ctx, 0, account, owner, true); !isTesSuccess(res))
return res;
return tesSUCCESS;
}
TER
ContractDelete::deleteContract(
ApplyView& view,
std::shared_ptr<SLE> const& sle,
AccountID const& account,
beast::Journal j)
{
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
if (!view.dirRemove(keylet::ownerDir(account), (*sle)[sfOwnerNode], sle->key(), false))
{
// LCOV_EXCL_START
JLOG(j.trace()) << "Unable to delete Delegate from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
auto const sleOwner = view.peek(keylet::account(account));
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleOwner, -1, j);
view.erase(sle);
return tesSUCCESS;
}
TER
ContractDelete::doApply()
{
AccountID const account = ctx_.tx.getAccountID(sfAccount);
AccountID const contractAccount = ctx_.tx.isFieldPresent(sfContractAccount)
? ctx_.tx.getAccountID(sfContractAccount)
: account;
auto const caSle = ctx_.view().read(keylet::account(contractAccount));
if (!caSle)
{
JLOG(j_.trace()) << "ContractModify: Account does not exist.";
return tefBAD_LEDGER;
}
uint256 const contractID = caSle->getFieldH256(sfContractID);
auto const contractSle = ctx_.view().read(keylet::contract(contractID));
if (!contractSle)
{
JLOG(j_.trace()) << "ContractDelete: Contract does not exist.";
return tecNO_TARGET;
}
// Lower the reference count of the ContractSource or remove the source from
// the ledger.
uint256 const contractHash = contractSle->getFieldH256(sfContractHash);
auto oldSourceSle = ctx_.view().peek(keylet::contractSource(contractHash));
if (oldSourceSle->getFieldU64(sfReferenceCount) == 1)
{
// NOTE: We dont adjust the owner count because ContractSource is an
// unowned object.
ctx_.view().erase(oldSourceSle);
}
else
{
oldSourceSle->setFieldU64(
sfReferenceCount, oldSourceSle->getFieldU64(sfReferenceCount) - 1);
ctx_.view().update(oldSourceSle);
}
AccountID const owner = contractSle->getAccountID(sfOwner);
STAmount const contractBalance = (*caSle)[sfBalance];
if (auto const res = deleteDoApply(ctx_, contractBalance, contractAccount, owner);
!isTesSuccess(res))
return res;
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -0,0 +1,352 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/ContractUtils.h>
#include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/digest.h>
#include <xrpl/tx/transactors/contract/ContractModify.h>
namespace xrpl {
XRPAmount
ContractModify::calculateBaseFee(ReadView const& view, STTx const& tx)
{
XRPAmount const maxAmount{std::numeric_limits<XRPAmount::value_type>::max()};
XRPAmount createFee{0};
if (tx.isFieldPresent(sfCreateCode))
createFee = XRPAmount{contract::contractCreateFee(tx.getFieldVL(sfCreateCode).size())};
if (createFee > maxAmount - view.fees().increment)
{
JLOG(debugLog().trace()) << "ContractModify: Create fee overflow detected.";
return XRPAmount{INITIAL_XRP};
}
auto baseFee = Transactor::calculateBaseFee(view, tx);
if (baseFee > maxAmount - createFee)
{
JLOG(debugLog().trace()) << "ContractModify: Total fee overflow detected.";
return XRPAmount{INITIAL_XRP};
}
return createFee + baseFee;
}
NotTEC
ContractModify::preflight(PreflightContext const& ctx)
{
auto const flags = ctx.tx.getFlags();
if (flags & tfUniversalMask)
{
JLOG(ctx.j.trace()) << "ContractModify: only tfUniversalMask is allowed.";
return temINVALID_FLAG;
}
// Either ContractCode or ContractHash must be present.
if (ctx.tx.isFieldPresent(sfContractCode) && ctx.tx.isFieldPresent(sfContractHash))
{
JLOG(ctx.j.trace()) << "ContractModify: Both ContractCode and ContractHash present";
return temMALFORMED;
}
// Validate Functions & Function Parameters.
if (auto const res = contract::preflightFunctions(ctx.tx, ctx.j); !isTesSuccess(res))
return res;
// Validate Instance Parameters.
if (auto const res = contract::preflightInstanceParameters(ctx.tx, ctx.j); !isTesSuccess(res))
return res;
// Validate Instance Parameter Values.
if (auto const res = contract::preflightInstanceParameterValues(ctx.tx, ctx.j);
!isTesSuccess(res))
return res;
if (ctx.tx.isFieldPresent(sfOwner))
{
if (ctx.tx.getAccountID(sfOwner) == ctx.tx.getAccountID(sfAccount))
return temMALFORMED;
if (ctx.tx.getAccountID(sfOwner) == ctx.tx.getAccountID(sfContractAccount))
return temMALFORMED;
}
return tesSUCCESS;
}
TER
ContractModify::preclaim(PreclaimContext const& ctx)
{
AccountID const account = ctx.tx.getAccountID(sfAccount);
AccountID const contractAccount =
ctx.tx.isFieldPresent(sfContractAccount) ? ctx.tx.getAccountID(sfContractAccount) : account;
auto const contractAccountSle = ctx.view.read(keylet::account(contractAccount));
if (!contractAccountSle)
{
JLOG(ctx.j.trace()) << "ContractModify: Contract Account does not exist.";
return tecNO_TARGET;
}
uint256 const contractID = contractAccountSle->getFieldH256(sfContractID);
auto const contractSle = ctx.view.read(keylet::contract(contractID));
if (!contractSle)
{
JLOG(ctx.j.trace()) << "ContractModify: Contract does not exist.";
return tecNO_TARGET;
}
if (ctx.tx.isFieldPresent(sfContractAccount) && contractSle->getAccountID(sfOwner) != account)
{
JLOG(ctx.j.trace()) << "ContractModify: Cannot modify a contract that "
"does not belong to the account.";
return tecNO_PERMISSION;
}
std::uint32_t flags = contractSle->getFlags();
// Check if the contract is immutable.
if (flags & tfImmutable)
{
JLOG(ctx.j.trace()) << "ContractModify: Contract is immutable.";
return tecNO_PERMISSION;
}
// Check if the contract code is immutable.
if (flags & tfCodeImmutable && ctx.tx.isFieldPresent(sfContractCode))
{
JLOG(ctx.j.trace()) << "ContractModify: ContractCode is immutable.";
return tecNO_PERMISSION;
}
// Check if the contract ABI is immutable.
if (flags & tfABIImmutable)
{
if (!ctx.tx.isFieldPresent(sfContractCode))
{
JLOG(ctx.j.trace()) << "ContractModify: ContractCode must be "
"present when modifying ABI.";
return tecNO_PERMISSION;
}
if (!ctx.tx.isFieldPresent(sfFunctions))
{
JLOG(ctx.j.trace()) << "ContractModify: Functions must be present "
"when modifying ABI.";
return tecNO_PERMISSION;
}
JLOG(ctx.j.trace()) << "ContractModify: ABI is immutable.";
return tecNO_PERMISSION;
}
// Can only include 1 of the 3 flags: tfCodeImmutable, tfABIImmutable,
// tfImmutable.
if ((flags & (tfCodeImmutable | tfABIImmutable | tfImmutable)) > tfImmutable)
{
JLOG(ctx.j.trace()) << "ContractModify: Cannot set more than one immutability flag.";
return temINVALID_FLAG;
}
bool isInstall = ctx.tx.isFieldPresent(sfContractHash);
auto contractHash = ctx.tx.at(~sfContractHash);
if (ctx.tx.isFieldPresent(sfContractCode))
{
xrpl::Blob wasmBytes = ctx.tx.getFieldVL(sfContractCode);
if (wasmBytes.empty())
{
JLOG(ctx.j.trace()) << "ContractModify: ContractCode provided is empty.";
return temMALFORMED;
}
contractHash = xrpl::sha512Half_s(xrpl::Slice(wasmBytes.data(), wasmBytes.size()));
if (ctx.view.exists(keylet::contractSource(*contractHash)))
isInstall = true;
// Iterate through the functions and validate them?
// HostFunctions mock;
// auto const re = preflightEscrowWasm(wasmBytes, "finish", {}, &mock,
// ctx.j); if (!isTesSuccess(re))
// {
// JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM";
// return re;
// }
}
// The ABI provided in Functions doesn't match the code.
if (isInstall)
{
auto const sle = ctx.view.read(keylet::contractSource(*contractHash));
if (!sle)
{
JLOG(ctx.j.trace()) << "ContractModify: ContractSource ledger object not found for "
"the provided ContractHash.";
return tefINTERNAL; // LCOV_EXCL_LINE
}
if (sle->isFieldPresent(sfInstanceParameters) &&
!ctx.tx.isFieldPresent(sfInstanceParameterValues))
{
JLOG(ctx.j.trace()) << "ContractModify: ContractHash is present, but "
"InstanceParameterValues is missing.";
return temMALFORMED;
}
auto const& instanceParams = sle->getFieldArray(sfInstanceParameters);
auto const& instanceParamValues = ctx.tx.getFieldArray(sfInstanceParameterValues);
if (auto const isValid =
contract::validateParameterMapping(instanceParams, instanceParamValues, ctx.j);
!isValid)
{
JLOG(ctx.j.trace()) << "ContractModify: InstanceParameters do not match what's in "
"the existing ContractSource ledger object.";
return temMALFORMED;
}
}
if (ctx.tx.isFieldPresent(sfOwner))
{
auto const ownerSle = ctx.view.read(keylet::account(ctx.tx.getAccountID(sfOwner)));
if (!ownerSle)
{
JLOG(ctx.j.trace()) << "ContractModify: New owner account does not exist.";
return tecNO_TARGET;
}
}
return tesSUCCESS;
}
TER
ContractModify::doApply()
{
AccountID const account = ctx_.tx.getAccountID(sfAccount);
AccountID const contractAccount = ctx_.tx.isFieldPresent(sfContractAccount)
? ctx_.tx.getAccountID(sfContractAccount)
: account;
auto const contractAccountSle = ctx_.view().read(keylet::account(contractAccount));
if (!contractAccountSle)
{
JLOG(ctx_.journal.trace()) << "ContractModify: Account does not exist.";
return tefINTERNAL;
}
uint256 const contractID = contractAccountSle->getFieldH256(sfContractID);
auto const contractSle = ctx_.view().peek(keylet::contract(contractID));
if (!contractSle)
{
JLOG(ctx_.journal.trace()) << "ContractModify: Contract does not exist.";
return tefINTERNAL;
}
auto currentSourceSle =
ctx_.view().peek(keylet::contractSource(contractSle->getFieldH256(sfContractHash)));
if (!currentSourceSle)
{
JLOG(ctx_.journal.trace()) << "ContractModify: ContractSource does not exist.";
return tefINTERNAL;
}
if (ctx_.tx.isFieldPresent(sfContractCode))
{
JLOG(ctx_.journal.trace()) << "ContractModify: Modifying ContractCode/ContractHash.";
xrpl::Blob wasmBytes = ctx_.tx.getFieldVL(sfContractCode);
auto const contractHash =
xrpl::sha512Half_s(xrpl::Slice(wasmBytes.data(), wasmBytes.size()));
auto const sourceKeylet = keylet::contractSource(contractHash);
auto sourceSle = ctx_.view().peek(sourceKeylet);
if (!sourceSle)
{
JLOG(ctx_.journal.trace()) << "ContractModify: Creating new ContractSource.";
// create the new ContractSource
sourceSle = std::make_shared<SLE>(sourceKeylet);
sourceSle->at(sfContractHash) = contractHash;
sourceSle->at(sfContractCode) = makeSlice(wasmBytes);
sourceSle->setFieldArray(sfFunctions, ctx_.tx.getFieldArray(sfFunctions));
if (ctx_.tx.isFieldPresent(sfInstanceParameters))
sourceSle->setFieldArray(
sfInstanceParameters, ctx_.tx.getFieldArray(sfInstanceParameters));
sourceSle->at(sfReferenceCount) = 1;
ctx_.view().insert(sourceSle);
}
// update the Contract
contractSle->setFieldH256(sfContractHash, contractHash);
if (ctx_.tx.isFieldPresent(sfInstanceParameterValues))
contractSle->setFieldArray(
sfInstanceParameterValues, ctx_.tx.getFieldArray(sfInstanceParameterValues));
ctx_.view().update(contractSle);
// update the existing ContractSource
if (currentSourceSle->getFieldU64(sfReferenceCount) == 1)
{
// remove the old ContractSource if no more references
ctx_.view().erase(currentSourceSle);
}
else
{
// decrement the reference count
currentSourceSle->setFieldU64(
sfReferenceCount, currentSourceSle->getFieldU64(sfReferenceCount) - 1);
ctx_.view().update(currentSourceSle);
}
}
else if (ctx_.tx.isFieldPresent(sfContractHash))
{
auto sourceSle =
ctx_.view().peek(keylet::contractSource(ctx_.tx.getFieldH256(sfContractHash)));
if (!sourceSle)
{
JLOG(ctx_.journal.trace()) << "ContractModify: ContractSource does not exist.";
return tefINTERNAL;
}
// set new contract hash
contractSle->setFieldH256(sfContractHash, ctx_.tx.getFieldH256(sfContractHash));
// set new instance parameter values if present
if (ctx_.tx.isFieldPresent(sfInstanceParameterValues))
contractSle->setFieldArray(
sfInstanceParameterValues, ctx_.tx.getFieldArray(sfInstanceParameterValues));
ctx_.view().update(contractSle);
sourceSle->setFieldU64(sfReferenceCount, sourceSle->getFieldU64(sfReferenceCount) + 1);
ctx_.view().update(sourceSle);
// update the existing ContractSource
if (currentSourceSle->getFieldU64(sfReferenceCount) == 1)
{
// remove the old ContractSource if no more references
ctx_.view().erase(currentSourceSle);
}
else
{
// decrement the reference count
currentSourceSle->setFieldU64(
sfReferenceCount, currentSourceSle->getFieldU64(sfReferenceCount) - 1);
ctx_.view().update(currentSourceSle);
}
}
else if (ctx_.tx.isFieldPresent(sfInstanceParameterValues))
{
// only updating instance parameter values
contractSle->setFieldArray(
sfInstanceParameterValues, ctx_.tx.getFieldArray(sfInstanceParameterValues));
ctx_.view().update(contractSle);
}
if (ctx_.tx.isFieldPresent(sfOwner))
{
contractSle->setAccountID(sfOwner, ctx_.tx.getAccountID(sfOwner));
ctx_.view().update(contractSle);
}
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -0,0 +1,32 @@
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/transactors/contract/ContractUserDelete.h>
namespace xrpl {
NotTEC
ContractUserDelete::preflight(PreflightContext const& ctx)
{
auto const flags = ctx.tx.getFlags();
if (flags & tfUniversalMask)
{
JLOG(ctx.j.trace()) << "ContractUserDelete: tfUniversalMask is not allowed.";
return temINVALID_FLAG;
}
return tesSUCCESS;
}
TER
ContractUserDelete::preclaim(PreclaimContext const& ctx)
{
return tesSUCCESS;
}
TER
ContractUserDelete::doApply()
{
return tesSUCCESS;
}
} // namespace xrpl

View File

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

View File

@@ -14,6 +14,10 @@
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/transactors/escrow/EscrowCreate.h>
#include <xrpl/tx/wasm/HostFunc.h>
#include <xrpl/tx/wasm/WasmVM.h>
#include <libxrpl/tx/transactors/escrow/EscrowHelpers.h>
namespace xrpl {
@@ -92,6 +96,29 @@ 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(sfFinishFunction))
{
// 10 base fees for the transaction (1 is in
// `Transactor::calculateBaseFee`), plus 5 drops per byte
txnFees += 9 * view.fees().base + 5 * tx[sfFinishFunction].size();
}
return txnFees;
}
bool
EscrowCreate::checkExtraFeatures(PreflightContext const& ctx)
{
if ((ctx.tx.isFieldPresent(sfFinishFunction) || ctx.tx.isFieldPresent(sfData)) &&
!ctx.rules.enabled(featureSmartEscrow))
return false;
return true;
}
NotTEC
EscrowCreate::preflight(PreflightContext const& ctx)
{
@@ -123,12 +150,19 @@ EscrowCreate::preflight(PreflightContext const& ctx)
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
return temBAD_EXPIRATION;
if (ctx.tx.isFieldPresent(sfFinishFunction) && !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[~sfFinishFunction])
{
JLOG(ctx.j.debug()) << "Must have at least one of FinishAfter, "
"Condition, or FinishFunction.";
return temMALFORMED;
}
if (auto const cb = ctx.tx[~sfCondition])
{
@@ -144,6 +178,60 @@ EscrowCreate::preflight(PreflightContext const& ctx)
}
}
if (ctx.tx.isFieldPresent(sfData))
{
if (!ctx.tx.isFieldPresent(sfFinishFunction))
{
JLOG(ctx.j.debug()) << "EscrowCreate with Data requires FinishFunction";
return temMALFORMED;
}
auto const data = ctx.tx.getFieldVL(sfData);
if (data.size() > maxWasmDataLength)
{
JLOG(ctx.j.debug()) << "EscrowCreate.Data bad size " << data.size();
return temMALFORMED;
}
}
if (ctx.tx.isFieldPresent(sfFinishFunction))
{
auto const fees(ctx.registry.get().getFees());
if (fees.extensionSizeLimit == 0 || fees.extensionComputeLimit == 0)
{
JLOG(ctx.j.debug()) << "WASM runtime deactivated by fee voting";
return temTEMP_DISABLED;
}
auto const code = ctx.tx.getFieldVL(sfFinishFunction);
if (code.size() == 0 || code.size() > fees.extensionSizeLimit)
{
JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction 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(sfFinishFunction))
{
auto const code = ctx.tx.getFieldVL(sfFinishFunction);
// basic checks happen in `preflight`
HostFunctions mock(ctx.j);
auto const re = preflightEscrowWasm(code, mock, ESCROW_FUNCTION_NAME);
if (!isTesSuccess(re))
{
JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM";
return re;
}
}
return tesSUCCESS;
}
@@ -395,8 +483,9 @@ EscrowCreate::doApply()
// Check reserve and funds availability
STAmount const amount{ctx_.tx[sfAmount]};
auto const reserveToAdd = calculateAdditionalReserve(ctx_.tx[~sfFinishFunction]);
auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + reserveToAdd);
auto const balance = sle->getFieldAmount(sfBalance).xrp();
if (balance < reserve)
@@ -430,6 +519,8 @@ EscrowCreate::doApply()
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
(*slep)[~sfFinishFunction] = ctx_.tx[~sfFinishFunction];
(*slep)[~sfData] = ctx_.tx[~sfData];
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
{
@@ -497,7 +588,7 @@ EscrowCreate::doApply()
}
// increment owner count
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
adjustOwnerCount(ctx_.view(), sle, reserveToAdd, ctx_.journal);
ctx_.view().update(sle);
return tesSUCCESS;
}

View File

@@ -14,6 +14,8 @@
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/transactors/escrow/EscrowFinish.h>
#include <xrpl/tx/wasm/HostFuncImpl.h>
#include <xrpl/tx/wasm/WasmVM.h>
#include <libxrpl/tx/transactors/escrow/EscrowHelpers.h>
@@ -48,7 +50,14 @@ 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(sfComputationAllowance) && !ctx.rules.enabled(featureSmartEscrow))
{
return false;
}
return true;
}
NotTEC
@@ -60,7 +69,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[~sfComputationAllowance]; allowance)
{
auto const fees(ctx.registry.get().getFees());
if (fees.extensionComputeLimit == 0)
{
JLOG(ctx.j.debug()) << "WASM runtime deactivated by fee voting";
return temTEMP_DISABLED;
}
if (*allowance == 0)
{
return temBAD_LIMIT;
}
if (*allowance > fees.extensionComputeLimit)
{
JLOG(ctx.j.debug()) << "ComputationAllowance too large: " << *allowance;
return temBAD_LIMIT;
}
}
if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
return err;
return tesSUCCESS;
}
@@ -94,9 +128,6 @@ EscrowFinish::preflightSigValidated(PreflightContext const& ctx)
}
}
if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
return err;
return tesSUCCESS;
}
@@ -109,7 +140,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[~sfComputationAllowance]; 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) / MICRO_DROPS_PER_DROP + 1;
extraFee += allowanceFee;
}
return Transactor::calculateBaseFee(view, tx) + extraFee;
}
@@ -185,25 +224,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(sfFinishFunction))
{
if (!ctx.tx.isFieldPresent(sfComputationAllowance))
{
JLOG(ctx.j.debug()) << "FinishFunction requires ComputationAllowance";
return tefWASM_FIELD_NOT_INCLUDED;
}
}
else
{
if (ctx.tx.isFieldPresent(sfComputationAllowance))
{
JLOG(ctx.j.debug()) << "FinishFunction not present, "
"ComputationAllowance present";
return tefNO_WASM;
}
}
}
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;
@@ -216,7 +280,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;
@@ -234,6 +299,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(), account_, destID, sled, ctx_.journal);
!isTesSuccess(err))
return err;
}
// Check cryptocondition fulfillment
{
auto const id = ctx_.tx.getTransactionID();
@@ -287,15 +366,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(), account_, destID, sled, ctx_.journal);
!isTesSuccess(err))
return err;
if (auto err =
verifyDepositPreauth(ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
!isTesSuccess(err))
return err;
}
// Execute custom release function
if ((*slep)[~sfFinishFunction])
{
JLOG(j_.trace()) << "The escrow has a finish function, running WASM code...";
// WASM execution
auto const wasmStr = slep->getFieldVL(sfFinishFunction);
std::vector<uint8_t> const wasm(wasmStr.begin(), wasmStr.end());
WasmHostFunctionsImpl ledgerDataProvider(ctx_, k);
if (!ctx_.tx.isFieldPresent(sfComputationAllowance))
{
// already checked above, this check is just in case
return tecINTERNAL;
}
std::uint32_t const allowance = ctx_.tx[sfComputationAllowance];
auto re = runEscrowWasm(wasm, ledgerDataProvider, allowance, ESCROW_FUNCTION_NAME);
JLOG(j_.trace()) << "Escrow WASM ran";
if (auto const& data = ledgerDataProvider.getData(); data.has_value())
{
if (data->size() > maxWasmDataLength)
{
// 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_.setWasmReturnCode(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 tecWASM_REJECTED;
}
}
else
{
JLOG(j_.debug()) << "WASM Failure: " + transHuman(re.error());
return re.error();
}
}
AccountID const account = (*slep)[sfAccount];
@@ -372,9 +507,11 @@ EscrowFinish::doApply()
ctx_.view().update(sled);
auto const reserveToSubtract = calculateAdditionalReserve((*slep)[~sfFinishFunction]);
// Adjust source owner count
auto const sle = ctx_.view().peek(keylet::account(account));
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
adjustOwnerCount(ctx_.view(), sle, -1 * reserveToSubtract, ctx_.journal);
ctx_.view().update(sle);
// Remove escrow from ledger

View File

@@ -227,4 +227,18 @@ escrowUnlockApplyHelper<MPTIssue>(
journal);
}
// calculateAdditionalReserve computes the owner count impact of an Escrow.
// An escrow without a FinishFunction costs 1 reserve. With a FinishFunction,
// each additional 500 bytes beyond the first 500 adds another reserve slot.
template <class T>
inline uint32_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

@@ -1,10 +1,10 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/NFTokenUtils.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/transactors/nft/NFTokenAcceptOffer.h>
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
namespace xrpl {
@@ -415,7 +415,7 @@ NFTokenAcceptOffer::acceptOffer(std::shared_ptr<SLE> const& offer)
}
// Now transfer the NFT:
return transferNFToken(buyer, seller, nftokenID);
return nft::transferNFToken(ctx_.view(), buyer, seller, nftokenID);
}
TER
@@ -538,7 +538,7 @@ NFTokenAcceptOffer::doApply()
}
// Now transfer the NFT:
return transferNFToken(buyer, seller, nftokenID);
return nft::transferNFToken(ctx_.view(), buyer, seller, nftokenID);
}
if (bo)

View File

@@ -1,8 +1,8 @@
#include <xrpl/ledger/helpers/NFTokenUtils.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/transactors/nft/NFTokenBurn.h>
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
namespace xrpl {

View File

@@ -1,8 +1,8 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/NFTokenUtils.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/transactors/nft/NFTokenCancelOffer.h>
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
#include <boost/endian/conversion.hpp>

View File

@@ -1,8 +1,8 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/NFTokenUtils.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/transactors/nft/NFTokenCreateOffer.h>
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
namespace xrpl {
@@ -38,6 +38,10 @@ NFTokenCreateOffer::preflight(PreflightContext const& ctx)
TER
NFTokenCreateOffer::preclaim(PreclaimContext const& ctx)
{
auto const sle = ctx.view.read(keylet::account(ctx.tx[sfAccount]));
auto const balance = sle ? (*sle)[sfBalance] : XRPAmount{0};
JLOG(ctx.j.error()) << "NFTokenCreateOffer::preclaim.Balance: " << balance;
if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
return tecEXPIRED;

View File

@@ -1,7 +1,7 @@
#include <xrpl/ledger/helpers/NFTokenUtils.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/transactors/nft/NFTokenModify.h>
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
namespace xrpl {

View File

@@ -1,8 +1,8 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/NFTokenUtils.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
#include <xrpl/tx/transactors/system/LedgerStateFix.h>
namespace xrpl {

View File

@@ -0,0 +1,32 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/mulDiv.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/tx/wasm/ContractContext.h>
namespace xrpl {
std::vector<ParameterValueVec>
getParameterValueVec(STArray const& functionParameters)
{
std::vector<ParameterValueVec> param_map;
for (auto const& param : functionParameters)
{
auto const& value = param.getFieldData(sfParameterValue);
param_map.emplace_back(value);
}
return param_map;
}
std::vector<ParameterTypeVec>
getParameterTypeVec(STArray const& functionParameters)
{
std::vector<ParameterTypeVec> param_map;
for (auto const& param : functionParameters)
{
auto const& type = param.getFieldDataType(sfParameterType);
param_map.emplace_back(type);
}
return param_map;
}
} // namespace xrpl

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