Compare commits

...

111 Commits

Author SHA1 Message Date
Shawn Xie
5229ff5a45 fix: fix pipeline failures (#6850) 2026-04-10 13:42:59 -04:00
Shawn Xie
6a8016f288 Merge develop into confidential-transfer 2026-04-09 11:54:29 -04:00
Shawn Xie
5d38603f90 fix compile and test failure 2026-04-09 11:22:31 -04:00
Shawn Xie
6f4fddc1ae Merge remote-tracking branch 'upstream/develop' into ct-merge-mpt-dex 2026-04-09 11:05:31 -04:00
Peter Chen
4ae17f193f Add JS fields required for Clio mpt_holder_api (#6810) 2026-04-09 10:49:10 -04:00
Gregory Tsipenyuk
56c9d1d497 fix: Add description for terLOCKED error (#6811) 2026-04-08 20:56:19 +00:00
yinyiqian1
d52dd29d20 fix: Address AI reviewer comments for Permission Delegation (#6675) 2026-04-08 20:22:19 +00:00
Peter Chen
36c805692a add batch tests (#6598) 2026-04-08 14:45:11 -04:00
Mayukha Vadari
7793b5f10b refactor: Combine AMMHelpers and AMMUtils (#6733) 2026-04-08 17:38:33 +00:00
yinyiqian1
5bd7ce1dfd Add tests for confidential delegation with tickets (#6808) 2026-04-08 13:20:59 -04:00
Gregory Tsipenyuk
dfcad69155 feat: Add MPT support to DEX (#5285) 2026-04-08 16:17:37 +00:00
yinyiqian1
0d7bf6948e Add delegation tests for Confidential Transfers (#6729) 2026-04-07 17:45:26 -04:00
Pratik Mankawde
6d1a5be8d2 fix: Handle WSClient write failure when server closes WebSocket (#6671)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 19:15:40 +00:00
Peter Chen
58e38e05f9 add non-curve point tests (#6695) 2026-04-07 13:50:08 -04:00
Shawn Xie
697786afab Merge develop into confidential-transfer
- Resolved conflicts from autogen files and modularization
2026-04-06 15:45:06 -04:00
Shawn Xie
834cb72515 Update autogen files and delegable count 2026-04-06 15:00:07 -04:00
Shawn Xie
261e7d7769 Resolve conflicts 2026-04-06 12:11:45 -04:00
Shawn Xie
042b09563b Merge remote-tracking branch 'upstream/develop' into ct-merge 2026-04-06 11:57:06 -04:00
yinyiqian1
cc2d6cbff6 Integrate mpt_utility SDK for tests (#6705) 2026-04-02 12:04:05 -04:00
yinyiqian1
6f1232140b remove dead functions due to integration (#6683) 2026-03-30 16:37:55 -04:00
Peter Chen
13b3a243a3 add tests: encrypt under wrong public key (#6650) 2026-03-30 10:38:03 -04:00
yinyiqian1
2472ef7df7 Integrate mpt-crypto SDK lib for on-chain verification (#6679) 2026-03-27 16:26:37 -04:00
Shawn Xie
b34ecc476a Add ProofReader and auth/lock and overflow edge case checks (#6651) 2026-03-26 13:16:51 -04:00
Shawn Xie
4c0e6012e3 Handle edge cases checks in flag setting and token deletion (#6596) 2026-03-24 11:41:27 -04:00
Peter Chen
3f749ecf76 tests: add ticket tests (#6576) 2026-03-20 10:52:18 -04:00
Shawn Xie
a43cf94ff7 update sfBlindingFactor to type uint256 and refactor helper functions to return std::optional 2026-03-19 14:04:03 -04:00
Shawn Xie
9f4cf28aea Rename privacy flag and public key names (#6550)
Corresponding spec change:
https://github.com/XRPLF/XRPL-Standards/pull/501

### Field Renames (SFields)

| Before | After |
|--------|-------|
| `sfIssuerElGamalPublicKey` | `sfIssuerEncryptionKey` |
| `sfHolderElGamalPublicKey` | `sfHolderEncryptionKey` |
| `sfAuditorElGamalPublicKey` | `sfAuditorEncryptionKey` |

### Flag Renames

#### Transaction Flags (`tf`)

| Before | After |
|--------|-------|
| `tfMPTCanPrivacy` | `tfMPTCanConfidentialAmount` |

#### Ledger State Flags (`lsf`)

| Before | After |
|--------|-------|
| `lsfMPTCanPrivacy` | `lsfMPTCanConfidentialAmount` |

#### Ledger State Mutable Flags (`lsmf`)

| Before | After |
|--------|-------|
| `lsmfMPTCannotMutatePrivacy` |
`lsmfMPTCannotMutateCanConfidentialAmount` |

#### Transaction Mutable Flags (`tmf`)

| Before | After |
|--------|-------|
| `tmfMPTCannotMutatePrivacy` |
`tmfMPTCannotMutateCanConfidentialAmount` |
| `tmfMPTSetPrivacy` | `tmfMPTSetCanConfidentialAmount` |
| `tmfMPTClearPrivacy` | `tmfMPTClearCanConfidentialAmount` |
2026-03-17 10:53:17 -04:00
Peter Chen
c45177b69e add homomorphic tests (#6490) 2026-03-16 11:27:36 -04:00
Shawn Xie
d615098849 refactor: improve readability and clean up code from review comments (#6544) 2026-03-16 10:47:39 -04:00
Shawn Xie
eeb0d15ea9 refactor: return optional buffers for helper functions (#6520) 2026-03-11 12:02:27 -04:00
Shawn Xie
84cc8599af chore: Apply clang-format width 100 (#6516) 2026-03-10 11:24:01 -04:00
Shawn Xie
36c1c5f3cd Merge develop into confidential-transfer 2026-03-09 16:50:01 -04:00
Shawn Xie
2c94e213c2 Resolve conflicts 2026-03-09 15:32:42 -04:00
Shawn Xie
cbcc75ff2d Merge remote-tracking branch 'upstream/develop' into ct-merge-dev-100col 2026-03-09 15:25:10 -04:00
Shawn Xie
da7698974c Merge commit '2c1fad102353e11293e3edde1c043224e7d3e983' into ct-merge-dev-100col 2026-03-09 14:24:26 -04:00
Shawn Xie
a3a82faa3d fix conflicts and update modularization 2026-03-09 14:23:22 -04:00
Shawn Xie
fff83c1d4a Merge commit '25cca465538a56cce501477f9e5e2c1c7ea2d84c' into ct-merge-dev-100col 2026-03-09 14:02:31 -04:00
Shawn Xie
803ab67fc5 fix: address auditor feedbacks (#6511) 2026-03-09 13:59:32 -04:00
yinyiqian1
734b11c0e3 Support shared r for ConfidentialMPTSend equality proof (#6496)
* Support shared r for send equality proof
2026-03-06 18:28:35 -05:00
Peter Chen
fae34d0f36 tests: add replay tests to confidential MPT (#6451) 2026-03-04 13:43:43 -05:00
yinyiqian1
c52d317810 Update hashing and support ticket (#6444) 2026-02-27 11:50:22 -05:00
Peter Chen
c2f8b91397 Add invariants and tests (#6403) 2026-02-25 16:40:44 -05:00
Peter Chen
1ea9312946 update crypto-lib (#6418) 2026-02-25 12:18:31 -05:00
yinyiqian1
6ad60d7141 Support Range Proof for ConfidentialMPTSend (#6404)
- proving send amount m is in the range [0, 2^64)
- proving remaining balance b-m is in the range [0, 2^64)
2026-02-20 14:18:34 -05:00
Shawn Xie
94e911ed69 Add Range Proof Verification to ConvertBack (#6377) 2026-02-19 19:22:48 -05:00
Shawn Xie
b2c434dd73 Compress ElGamal Public Keys and Pedersen Commitments + Add Validation (#6385) 2026-02-19 08:41:15 -05:00
Peter Chen
b6d1a8d62b Test Auditor for Confidential Send and revealed R (#6320) 2026-02-17 14:27:33 -05:00
Shawn Xie
9d0c854139 Remove hardcoded library functions in ConfidentialTransfer (#6365) 2026-02-12 14:18:36 -05:00
Ayaz Salikhov
3a6ca681ff chore: Use mpt-crypto library (#6362) 2026-02-12 12:59:36 -05:00
Shawn Xie
a216824c15 Merge develop into confidential-transfer 2026-02-12 11:40:07 -05:00
Shawn Xie
90cf86a920 remove newline 2026-02-12 11:33:34 -05:00
Shawn Xie
e69d3c9bd7 Merge remote-tracking branch 'upstream/develop' into ct-merge-develop-lib 2026-02-12 11:14:40 -05:00
Shawn Xie
fd390a4f1c Add doxygen comments for new transactions and helper functions (#6332) 2026-02-10 10:51:50 -05:00
Shawn Xie
3941283438 Prefix confidential transfer transaction names with "MPT" (#6312) 2026-02-02 12:13:18 -05:00
Shawn Xie
86af28d91d Apply clang-format due to new column size (#6311) 2026-02-02 11:15:39 -05:00
Shawn Xie
41f7102fb8 Merge develop into ripple/confidential-transfer
Merge `develop` into `ripple/confidential-transfer`
2026-02-02 09:46:25 -05:00
Shawn Xie
66ed0fa452 namespace rename 2026-01-30 12:36:15 -05:00
Shawn Xie
cad8fb328a Merge branch 'develop' into ct-merge-develop-new 2026-01-30 12:25:55 -05:00
Shawn Xie
31346425f0 Merge commit '5f638f55536def0d88b970d1018a465a238e55f4' into ct-merge-develop-new 2026-01-30 12:25:36 -05:00
Shawn Xie
40bfaa25d2 Merge commit '92046785d1fea5f9efe5a770d636792ea6cab78b' into ct-merge-develop-new 2026-01-30 12:25:16 -05:00
Peter Chen
c4916f1251 Add more Auditor Tests for Convert and ConvertBack (#6255) 2026-01-29 12:17:19 -05:00
yinyiqian1
fc8b7898c5 Support Pedersen-ElGamal linkage for ConfidentialSend (#6289)
* support Pedersen Amount commitment for ConfidentialSend
* support Pedersen Balance commitment for ConfidentialSend
2026-01-29 11:18:46 -05:00
Shawn Xie
446f9fbe6d Reuse getConfidentialRecipientCount (#6281) 2026-01-26 13:04:02 -05:00
yinyiqian1
1297385b7e Support ConfidentialSend equality proof (#6274)
* Support ConfidentialSend equality proof

* resolve conflicts

* Add version check in send
2026-01-26 12:39:35 -05:00
Shawn Xie
114adc0c57 Pedersen commitment with ConvertBack and basic test (#6243) 2026-01-22 13:00:19 -05:00
yinyiqian1
1d349c32c5 fix encrypt zero balance and remove improper throw (#6242) 2026-01-20 12:27:44 -05:00
Shawn Xie
a5f20c129d Copying over pedersen commitment from crypto lib (#6238) 2026-01-19 13:56:10 -05:00
yinyiqian1
75d143a2a0 support new design to reveal blinding factor (#6237)
* reveal blinding factor and optimize
* schnorr proof is added for registering holder pub key
* clean env.close that already closed
* clean up the lib functions
2026-01-19 13:07:19 -05:00
Shawn Xie
e3da98e310 Update unit test framework to use shared random factor (#6233) 2026-01-16 16:36:49 -05:00
Shawn Xie
ec6d7cb91d Add equality proof to ConvertBack and refactor to reduce redundancy (#6220) 2026-01-16 10:28:55 -05:00
Shawn Xie
fa055c2bd5 Add auditing feature across confidential transfer transactions (#6200) 2026-01-14 11:18:06 -05:00
Shawn Xie
6c38086f17 ConfidentialConvert with Equality Proof (#6177) 2026-01-07 16:17:07 -05:00
Shawn Xie
3e9dc276ed add back clawback hash (#6175) 2026-01-06 12:21:00 -05:00
Shawn Xie
abf7a62b1f Refactor proof (#6168) 2026-01-05 12:00:41 -05:00
yinyiqian1
bd3a6e1631 Support equality proof for confidential clawback (#6149) 2026-01-02 11:48:06 -05:00
yinyiqian1
7c0bd419a4 support mutability for MPTPrivacy (#6137)
Update lsfMPTNoConfidentialTransfer to lsfMPTPrivacy
Add flag lsmfMPTPrivacy to control the mutability of lsfMPTPrivacy.
disallow mutating lsfMPTPrivacy when lsfMPTPrivacy is not set.
disallow mutating lsfMPTPrivacy when there's confidential outstanding amount.
2025-12-10 17:10:33 -05:00
yinyiqian1
d3126959e7 Merge pull request #6123 from yinyiqian1/merge
Merge remote-tracking branch 'origin/develop' into 'ripple/confidential-transfer'
2025-12-09 10:49:11 -05:00
yinyiqian1
67e8e89e0f copyright fix 2025-12-08 18:36:34 -05:00
yinyiqian1
4e4326a174 trigger ci 2025-12-08 18:25:47 -05:00
yinyiqian1
5397bd6d6e fix naming 2025-12-08 17:58:32 -05:00
yinyiqian1
6dece25cc3 fix test failure 2025-12-08 17:34:20 -05:00
yinyiqian1
d9da8733be resolve pre-commit clang-format 2025-12-08 16:45:41 -05:00
yinyiqian1
f6f51451e7 Merge remote-tracking branch 'origin/develop' into merge 2025-12-08 15:09:04 -05:00
Shawn Xie
b94c95b3e9 Change err code (#6050) 2025-11-18 14:19:41 -05:00
yinyiqian1
8365148b5c feat: support ConfidentialClawback and add tests (#6023) 2025-11-13 14:24:40 -05:00
Shawn Xie
c03866bf0f Variable rename (#6028) 2025-11-12 11:58:05 -05:00
Shawn Xie
389afc5f06 Add deposit preauth and other checks (#6011) 2025-11-10 10:52:23 -05:00
Shawn Xie
7b04eaae81 ConvertBack preclaim tests (#6006) 2025-11-05 13:58:52 -05:00
Shawn Xie
1343019509 ConvertBack tests (#6005) 2025-11-05 13:52:55 -05:00
Shawn Xie
cd75e630a2 Change ConfidentialSend preflight error code (#5994) 2025-11-03 18:46:27 -05:00
Shawn Xie
ec57fbdc5f Merge remote-tracking branch 'upstream/develop' into confidential-transfer 2025-11-03 18:42:41 -05:00
Shawn Xie
4fe67f5715 ConvertBack preflight tests (#5991) 2025-11-03 15:58:32 -05:00
Shawn Xie
44d885e39b Basic ConvertBack test (#5979) 2025-10-31 11:46:24 -04:00
yinyiqian1
3af758145c Check auth for ConfidentialSend (#5968) 2025-10-30 11:02:46 -04:00
yinyiqian1
f3d4d4341b add ciphertext check for ConfidentialSend (#5964) 2025-10-29 12:10:48 -04:00
Shawn Xie
ddb518ad09 MergeInbox tests (#5949) 2025-10-28 13:21:11 -04:00
Shawn Xie
3899e3f36c Add auth checks for convert (#5937) 2025-10-24 11:42:43 -04:00
yinyiqian1
e4a8ba51f9 check lock in ConfidentialSend (#5933) 2025-10-23 12:58:38 -04:00
Shawn Xie
35e4fad557 Add ciphertext check (#5930) 2025-10-23 11:57:18 -04:00
yinyiqian1
8e9cb3c1da support ConfidentialSend (#5921) 2025-10-22 12:02:00 -04:00
Shawn Xie
18d92058e3 MergeInbox (#5922) 2025-10-22 11:30:44 -04:00
Shawn Xie
f24d584f29 ConfidentialConvert tests (#5911) 2025-10-20 14:39:16 -04:00
Shawn Xie
da3fbcd25b Remove unused header file (#5908) 2025-10-17 16:42:08 -04:00
Shawn Xie
daa1303b5a Update decryption test helper function (#5907) 2025-10-17 14:19:19 -04:00
Shawn Xie
a636fe5871 Update test framework for encryption (#5906) 2025-10-17 14:04:54 -04:00
Shawn Xie
bbc3071fd1 Update mpt-crypto with zero encryption (#5905) 2025-10-17 11:41:39 -04:00
Shawn Xie
8fdc639206 ConfidentialConvert (#5901)
ConfidentialConvert and some test framework update
2025-10-16 14:31:14 -04:00
Shawn Xie
5a89641d98 remove duplicate code 2025-10-07 15:52:18 -04:00
Shawn Xie
beefa248a6 Merge remote-tracking branch 'upstream/develop' into confidential-transfer 2025-10-07 15:00:14 -04:00
Shawn Xie
e919a25ecb Merge develop into ripple/confidential-transfer (#5835)
* Fix: Don't flag consensus as stalled prematurely (#5658)

Fix stalled consensus detection to prevent false positives in situations where there are no disputed transactions.

Stalled consensus detection was added to 2.5.0 in response to a network consensus halt that caused a round to run for over an hour. However, it has a flaw that makes it very easy to have false positives. Those false positives are usually mitigated by other checks that prevent them from having an effect, but there have been several instances of validators "running ahead" because there are circumstances where the other checks are "successful", allowing the stall state to be checked.

* Set version to 2.5.1

* fix: Skip processing transaction batch if the batch is empty (#5670)

Avoids an assertion failure in NetworkOPsImp::apply in the unlikely event that all incoming transactions are invalid.

* Fix: EscrowTokenV1 (#5571)

* resolves an accounting inconsistency in MPT escrows where transfer fees were not properly handled when unlocking escrowed tokens.

* refactor: Wrap GitHub CI conditionals in curly braces (#5796)

This change wraps all GitHub conditionals in `${{ .. }}`, both for consistency and to reduce unexpected failures, because it was previously noticed that not all conditionals work without those curly braces.

* Only notify clio for PRs targeting the release and master branches (#5794)

Clio should only be notified when releases are about to be made, instead of for all PR, so this change only notifies Clio when a PR targets the release or master branch.

* Support DynamicMPT XLS-94d (#5705)

* extends the functionality of the MPTokenIssuanceSet transaction, allowing the issuer to update fields or flags that were explicitly marked as mutable during creation.

* Bugfix: Adds graceful peer disconnection (#5669)

The XRPL establishes connections in three stages: first a TCP connection, then a TLS/SSL handshake to secure the connection, and finally an upgrade to the bespoke XRP Ledger peer-to-peer protocol. During connection termination, xrpld directly closes the TCP connection, bypassing the TLS/SSL shutdown handshake. This makes peer disconnection diagnostics more difficult - abrupt TCP termination appears as if the peer crashed rather than disconnected gracefully.

This change refactors the connection lifecycle with the following changes:
- Enhanced outgoing connection logic with granular timeouts for each connection stage (TCP, TLS, XRPL handshake) to improve diagnostic capabilities
- Updated both PeerImp and ConnectAttempt to use proper asynchronous TLS shutdown procedures for graceful connection termination

* Downgrade to boost 1.83

* Set version to 2.6.1-rc1

* chore: Use self hosted windows runners (#5780)

This changes switches from the GitHub-managed Windows runners to self-hosted runners to significantly reduce build time.

* Rename mutable flags (#5797)

This is a minor change on top of #5705

* fix(amendment): Add missing fields for keylets to ledger objects (#5646)

This change adds a fix amendment (`fixIncludeKeyletFields`) that adds:
* `sfSequence` to `Escrow` and `PayChannel`
* `sfOwner` to `SignerList`
* `sfOracleDocumentID` to `Oracle`

This ensures that all ledger entries hold all the information needed to determine their keylet.

* chore: Limits CI build and test parallelism to reduce resource contention (#5799)

GitHub runners have a limit on how many concurrent jobs they can actually process (even though they will try to run them all at the same time), and similarly the Conan remote cannot handle hundreds of concurrent requests. Previously, the Conan dependency uploading was already limited to max 10 jobs running in parallel, and this change makes the same change to the build+test workflow.

* chore: Build and test all configs for daily scheduled run (#5801)

This change re-enables building and testing all configurations, but only for the daily scheduled run. Previously all configurations were run for each merge into the develop branch, but that overwhelmed both the GitHub runners and the Conan remote, and thus they were limited to just a subset of configurations. Now that the number of jobs is limited via `max-parallel: 10`, we should be able to safely enable building all configurations again. However, building them all once a day instead of for each PR merge should be sufficient.

* chore: Add unit tests dir to code coverage excludes (#5803)

This change excludes unit test code from code coverage reporting.

* refactor: Modularise ledger (#5493)

This change moves the ledger code to libxrpl.

* Mark PermissionDelegation as unsupported

* Set version to 2.6.1-rc2

* Miscellaneous refactors and updates (#5590)

- Added a new Invariant: `ValidPseudoAccounts` which checks that all pseudo-accounts behave consistently through creation and updates, and that no "real" accounts look like pseudo-accounts (which means they don't have a 0 sequence). 
- `to_short_string(base_uint)`. Like `to_string`, but only returns the first 8 characters. (Similar to how a git commit ID can be abbreviated.) Used as a wrapped sink to prefix most transaction-related messages. More can be added later.
- `XRPL_ASSERT_PARTS`. Convenience wrapper for `XRPL_ASSERT`, which takes the `function` and `description` as separate parameters.
- `SField::sMD_PseudoAccount`. Metadata option for `SField` definitions to indicate that the field, if set in an `AccountRoot` indicates that account is a pseudo-account. Removes the need for hard-coded field lists all over the place. Added the flag to `AMMID` and `VaultID`.
- Added functionality to `SField` ctor to detect both code and name collisions using asserts. And require all SFields to have a name
- Convenience type aliases `STLedgerEntry::const_pointer` and `STLedgerEntry::const_ref`. (`SLE` is an alias to `STLedgerEntry`.)
- Generalized `feeunit.h` (`TaggedFee`) into `unit.h` (`ValueUnit`) and added new "BIPS"-related tags for future use. Also refactored the type restrictions to use Concepts.
- Restructured `transactions.macro` to do two big things
	1. Include the `#include` directives for transactor header files directly in the macro file. Removes the need to update `applySteps.cpp` and the resulting conflicts.
	2. Added a `privileges` parameter to the `TRANSACTION` macro, which specifies some of the operations a transaction is allowed to do. These `privileges` are enforced by invariant checks. Again, removed the need to update scattered lists of transaction types in various checks.
- Unit tests:
	1.  Moved more helper functions into `TestHelpers.h` and `.cpp`. 
	2. Cleaned up the namespaces to prevent / mitigate random collisions and ambiguous symbols, particularly in unity builds.
	3. Generalized `Env::balance` to add support for `MPTIssue` and `Asset`.
	4. Added a set of helper classes to simplify `Env` transaction parameter classes: `JTxField`, `JTxFieldWrapper`, and a bunch of classes derived or aliased from it. For an example of how awesome it is, check the changes `src/test/jtx/escrow.h` for how much simpler the definitions are for `finish_time`, `cancel_time`, `condition`, and `fulfillment`. 
	5. Generalized several of the amount-related helper classes to understand `Asset`s.
     6. `env.balance` for an MPT issuer will return a negative number (or 0) for consistency with IOUs.

* refactor: Simplify STParsedJSON with some helper functions (#5591)

- Add code coverage for STParsedJSON edge cases

Co-authored-by: Denis Angell <dangell@transia.co>

* test: Add STInteger and STParsedJSON tests (#5726)

This change is to improve code coverage (and to simplify #5720 and #5725); there is otherwise no change in functionality. The change adds basic tests for `STInteger` and `STParsedJSON`, so it becomes easier to test smaller changes to the types, as well as removes `STParsedJSONArray`, since it is not used anywhere (including in Clio).

* Revert "Update Conan dependencies: OpenSSL" (#5807)

This change reverts #5617, because it will require extensive testing that will take up more time than we have before the next scheduled release.

Reverting this change does not mean we are abandoning it. We aim to pick it back up once there's a sufficient time window to allow for testing on multiple distros running a mixture of OpenSSL 1.x and 3.x.

* docs: Add warning about using std::counting_semaphore (#5595)

This adds a comment to avoid using `std::counting_semaphore` until the minimum compiler versions of GCC and Clang have been updated to no longer contain the bug that is present in older compilers.

* Improve ValidatorList invalid UNL manifest logging (#5804)

This change raises logging severity from `INFO` to `WARN` when handling UNL manifest signed with an unexpected / invalid key. It also changes the internal error code for an invalid format of UNL manifest to `invalid` (from `untrusted`).

This is a follow up to problems experienced by an UNL node due to old manifest key configured in `validators.txt`, which would be easier to diagnose with improved logging.

It also replaces a log line with `UNREACHABLE` for an impossible situation when we match UNL manifest key against a configured key which has an invalid type (we cannot configure such a key because of checks when loading configured keys).

* chore: Pin all CI Docker tags (#5813)

To avoid surprises and ensure reproducibility, this change pins all CI Docker image tags to the latest version in the XRPLF/CI repo.

* change `fixPriceOracleOrder` to `Supported::yes` (#5749)

* fix: Address http header case sensitivity (#5767)

This change makes the regex in `HttpClient.cpp` that matches the content-length http header case insensitive to improve compatibility, as http headers are case insensitive.

* test: add more comprehensive tests for `FeeVote` (#5746)

This change adds more comprehensive tests for the `FeeVote` module, which previously only checked the basics, and not the more comprehensive flows in that class.

* ci: Call all reusable workflows reusable (#5818)

* Add `STInt32` as a new `SType` (#5788)

This change adds `STInt32` as a new `SType` under the `STInteger` umbrella, with `SType` value `12`. This is the first and only `STInteger` type that supports negative values.

* switch `fixIncludeKeyletFields` to `Supported::yes` (#5819)

* refactor: Restructure Transactor::preflight to reduce boilerplate (#5592)

* Restructures `Transactor::preflight` to create several functions that will remove the need for error-prone boilerplate code in derived classes' implementations of `preflight`.

* refactor: Add support for extra transaction signatures (#5594)

* Restructures Transactor signature checking code to be able to handle a `sigObject`, which may be the full transaction, or may be an object field containing a separate signature. Either way, the `sigObject` can be a single- or multi-sign signature.

* ci: Upload artifacts during build and test in a separate job (#5817)

* chore: Set free-form CI inputs as env vars (#5822)

This change moves CI values that could be user-provided into environment variables.

* Rename flags for DynamicMPT (#5820)

* Set version to 2.6.1

* fix: FD/handle guarding + exponential backoff (#5823)

* fix: Transaction sig checking functions do not get a full context (#5829)

Fixes a (currently harmless) bug introduced by PR #5594

* Remove bogus coverage warning (#5838)

* fix return type

---------

Co-authored-by: Ed Hennis <ed@ripple.com>
Co-authored-by: Jingchen <a1q123456@users.noreply.github.com>
Co-authored-by: Denis Angell <dangell@transia.co>
Co-authored-by: Bart <bthomee@users.noreply.github.com>
Co-authored-by: yinyiqian1 <yqian@ripple.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
Co-authored-by: Bronek Kozicki <brok@incorrekt.com>
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
Co-authored-by: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com>
Co-authored-by: tequ <git@tequ.dev>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2025-10-07 14:14:34 -04:00
Shawn Xie
c3fdbc0430 SFields and formats (#5795) 2025-10-01 17:02:11 +00:00
268 changed files with 52863 additions and 5026 deletions

View File

@@ -88,6 +88,7 @@ find_package(ed25519 REQUIRED)
find_package(gRPC REQUIRED)
find_package(LibArchive REQUIRED)
find_package(lz4 REQUIRED)
find_package(mpt-crypto REQUIRED)
find_package(nudb REQUIRED)
find_package(OpenSSL REQUIRED)
find_package(secp256k1 REQUIRED)
@@ -100,6 +101,7 @@ target_link_libraries(
INTERFACE
ed25519::ed25519
lz4::lz4
mpt-crypto::mpt-crypto
OpenSSL::Crypto
OpenSSL::SSL
secp256k1::secp256k1

View File

@@ -12,6 +12,7 @@
"protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12",
"openssl/3.6.1#e6399de266349245a4542fc5f6c71552%1774458290.139",
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1774883011.384",
"mpt-crypto/0.2.0-rc1#ed3f241f69d8b9ebf80069d1923d93a8%1773853481.755",
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
"libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492",
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03",
@@ -34,6 +35,7 @@
"msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649",
"m4/1.4.19#5d7a4994e5875d76faf7acf3ed056036%1774365463.87",
"cmake/4.3.0#b939a42e98f593fb34d3a8c5cc860359%1774439249.183",
"cmake/3.31.10#313d16a1aa16bbdb2ca0792467214b76%1765850153.479",
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1774439233.447",
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",
"autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86",
@@ -58,6 +60,12 @@
],
"lz4/[>=1.9.4 <2]": [
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504"
],
"openssl/3.5.5": [
"openssl/3.6.1"
],
"openssl/[>=3 <4]": [
"openssl/3.6.1"
]
},
"config_requires": []

View File

@@ -31,6 +31,7 @@ class Xrpl(ConanFile):
"ed25519/2015.03",
"grpc/1.78.1",
"libarchive/3.8.1",
"mpt-crypto/0.2.0-rc1",
"nudb/2.0.9",
"openssl/3.6.1",
"secp256k1/0.7.1",
@@ -214,6 +215,7 @@ class Xrpl(ConanFile):
"grpc::grpc++",
"libarchive::libarchive",
"lz4::lz4",
"mpt-crypto::mpt-crypto",
"nudb::nudb",
"openssl::crypto",
"protobuf::libprotobuf",

View File

@@ -44,6 +44,10 @@ suggestWords:
words:
- abempty
- AMMID
- AMMMPT
- AMMMPToken
- AMMMPTokens
- AMMXRP
- amt
- amts
- asnode
@@ -55,6 +59,7 @@ words:
- autobridging
- bimap
- bindir
- blindings
- bookdir
- Bougalis
- Britto
@@ -87,6 +92,7 @@ words:
- daria
- dcmake
- dearmor
- decryptor
- deleteme
- demultiplexer
- deserializaton
@@ -96,6 +102,8 @@ words:
- distro
- doxyfile
- dxrpl
- elgamal
- enabled
- endmacro
- exceptioned
- Falco
@@ -105,6 +113,7 @@ words:
- fmtdur
- fsanitize
- funclets
- Gamal
- gcov
- gcovr
- ghead
@@ -148,6 +157,8 @@ words:
- ltype
- mcmodel
- MEMORYSTATUSEX
- MPTAMM
- MPTDEX
- Merkle
- Metafuncton
- misprediction
@@ -157,6 +168,7 @@ words:
- mptid
- mptissuance
- mptissuanceid
- mptissue
- mptoken
- mptokenid
- mptokenissuance
@@ -191,6 +203,7 @@ words:
- partitioner
- paychan
- paychans
- Pedersen
- permdex
- perminute
- permissioned
@@ -227,6 +240,7 @@ words:
- sahyadri
- Satoshi
- scons
- Schnorr
- secp
- sendq
- seqit
@@ -254,6 +268,7 @@ words:
- stvar
- stvector
- stxchainattestations
- summands
- superpeer
- superpeers
- takergets

View File

@@ -213,11 +213,60 @@ public:
// Called when a credit is made to an account
// This is required to support PaymentSandbox
virtual void
creditHook(
creditHookIOU(
AccountID const& from,
AccountID const& to,
STAmount const& amount,
STAmount const& preCreditBalance)
{
XRPL_ASSERT(amount.holds<Issue>(), "creditHookIOU: amount is for Issue");
}
virtual void
creditHookMPT(
AccountID const& from,
AccountID const& to,
STAmount const& amount,
std::uint64_t preCreditBalanceHolder,
std::int64_t preCreditBalanceIssuer)
{
XRPL_ASSERT(amount.holds<MPTIssue>(), "creditHookMPT: amount is for MPTIssue");
}
/** Facilitate tracking of MPT sold by an issuer owning MPT sell offer.
* Unlike IOU, MPT doesn't have bi-directional relationship with an issuer,
* where a trustline limits an amount that can be issued to a holder.
* Consequently, the credit step (last MPTEndpointStep or
* BookStep buying MPT) might temporarily overflow OutstandingAmount.
* Limiting of a step's output amount in this case is delegated to
* the next step (in rev order). The next step always redeems when a holder
* account sells MPT (first MPTEndpointStep or BookStep selling MPT).
* In this case the holder account is only limited by the step's output
* and it's available funds since it's transferring the funds from one
* account to another account and doesn't change OutstandingAmount.
* This doesn't apply to an offer owned by an issuer.
* In this case the issuer sells or self debits and is increasing
* OutstandingAmount. Ability to issue is limited by the issuer
* originally available funds less already self sold MPT amounts (MPT sell
* offer).
* Consider an example:
* - GW creates MPT(USD) with 1,000USD MaximumAmount.
* - GW pays 950USD to A1.
* - A1 creates an offer 100XRP(buy)/100USD(sell).
* - GW creates an offer 100XRP(buy)/100USD(sell).
* - A2 pays 200USD to A3 with sendMax of 200XRP.
* Since the payment engine executes payments in reverse,
* OutstandingAmount overflows in MPTEndpointStep: 950 + 200 = 1,150USD.
* BookStep first consumes A1 offer. This reduces OutstandingAmount
* by 100USD: 1,150 - 100 = 1,050USD. GW offer can only be partially
* consumed because the initial available amount is 50USD = 1,000 - 950.
* BookStep limits it's output to 150USD. This in turn limits A3's send
* amount to 150XRP: A1 buys 100XRP and sells 100USD to A3. This doesn't
* change OutstandingAmount. GW buys 50XRP and sells 50USD to A3. This
* changes OutstandingAmount to 1,000USD.
*/
virtual void
issuerSelfDebitHookMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance)
{
}

View File

@@ -3,8 +3,8 @@
#include <xrpl/ledger/AcceptedLedgerTx.h>
#include <xrpl/ledger/BookListeners.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MultiApiJson.h>
#include <xrpl/protocol/UintTypes.h>
@@ -53,30 +53,30 @@ public:
issue. This is useful for pathfinding to find all possible next hops
from a given currency.
@param issue The issue to search for
@param asset The asset to search for
@param domain Optional domain restriction for the order book
@return Vector of books that want this issue
*/
virtual std::vector<Book>
getBooksByTakerPays(Issue const& issue, std::optional<Domain> const& domain = std::nullopt) = 0;
getBooksByTakerPays(Asset const& asset, std::optional<Domain> const& domain = std::nullopt) = 0;
/** Get the count of order books that want a specific issue.
@param issue The issue to search for
@param asset The asset to search for
@param domain Optional domain restriction for the order book
@return Number of books that want this issue
*/
virtual int
getBookSize(Issue const& issue, std::optional<Domain> const& domain = std::nullopt) = 0;
getBookSize(Asset const& asset, std::optional<Domain> const& domain = std::nullopt) = 0;
/** Check if an order book to XRP exists for the given issue.
@param issue The issue to check
@param asset The asset to check
@param domain Optional domain restriction for the order book
@return true if a book from this issue to XRP exists
*/
virtual bool
isBookToXRP(Issue const& issue, std::optional<Domain> const& domain = std::nullopt) = 0;
isBookToXRP(Asset const& asset, std::optional<Domain> const& domain = std::nullopt) = 0;
/**
* Process a transaction for order book tracking.

View File

@@ -15,10 +15,55 @@ namespace detail {
// into the PaymentSandbox class itself
class DeferredCredits
{
public:
struct Adjustment
private:
using KeyIOU = std::tuple<AccountID, AccountID, Currency>;
struct ValueIOU
{
Adjustment(STAmount const& d, STAmount const& c, STAmount const& b)
explicit ValueIOU() = default;
STAmount lowAcctCredits;
STAmount highAcctCredits;
STAmount lowAcctOrigBalance;
};
struct HolderValueMPT
{
HolderValueMPT() = default;
// Debit to issuer
std::uint64_t debit = 0;
std::uint64_t origBalance = 0;
};
struct IssuerValueMPT
{
IssuerValueMPT() = default;
std::map<AccountID, HolderValueMPT> holders;
// Credit to holder
std::uint64_t credit = 0;
// OutstandingAmount might overflow when MPTs are credited to a holder.
// Consider A1 paying 100MPT to A2 and A1 already having maximum MPTs.
// Since the payment engine executes a payment in revers, A2 is
// credited first and OutstandingAmount is going to be equal
// to MaximumAmount + 100MPT. In the next step A1 redeems 100MPT
// to the issuer and OutstandingAmount balances out.
std::int64_t origBalance = 0;
// Self debit on offer selling MPT. Since the payment engine executes
// a payment in reverse, a crediting/buying step may overflow
// OutstandingAmount. A sell MPT offer owned by a holder can redeem any
// amount up to the offer's amount and holder's available funds,
// balancing out OutstandingAmount. But if the offer's owner is issuer
// then it issues more MPT. In this case the available amount to issue
// is the initial issuer's available amount less all offer sell amounts
// by the issuer. This is self-debit, where the offer's owner,
// issuer in this case, debits to self.
std::uint64_t selfDebit = 0;
};
using AdjustmentMPT = IssuerValueMPT;
public:
struct AdjustmentIOU
{
AdjustmentIOU(STAmount const& d, STAmount const& c, STAmount const& b)
: debits(d), credits(c), origBalance(b)
{
}
@@ -29,16 +74,30 @@ public:
// Get the adjustments for the balance between main and other.
// Returns the debits, credits and the original balance
std::optional<Adjustment>
adjustments(AccountID const& main, AccountID const& other, Currency const& currency) const;
std::optional<AdjustmentIOU>
adjustmentsIOU(AccountID const& main, AccountID const& other, Currency const& currency) const;
std::optional<AdjustmentMPT>
adjustmentsMPT(MPTID const& mptID) const;
void
credit(
creditIOU(
AccountID const& sender,
AccountID const& receiver,
STAmount const& amount,
STAmount const& preCreditSenderBalance);
void
creditMPT(
AccountID const& sender,
AccountID const& receiver,
STAmount const& amount,
std::uint64_t preCreditBalanceHolder,
std::int64_t preCreditBalanceIssuer);
void
issuerSelfDebitMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance);
void
ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next);
@@ -52,21 +111,11 @@ public:
apply(DeferredCredits& to);
private:
// lowAccount, highAccount
using Key = std::tuple<AccountID, AccountID, Currency>;
struct Value
{
explicit Value() = default;
static KeyIOU
makeKeyIOU(AccountID const& a1, AccountID const& a2, Currency const& currency);
STAmount lowAcctCredits;
STAmount highAcctCredits;
STAmount lowAcctOrigBalance;
};
static Key
makeKey(AccountID const& a1, AccountID const& a2, Currency const& c);
std::map<Key, Value> credits_;
std::map<KeyIOU, ValueIOU> creditsIOU_;
std::map<MPTID, IssuerValueMPT> creditsMPT_;
std::map<AccountID, std::uint32_t> ownerCounts_;
};
@@ -131,16 +180,35 @@ public:
/** @} */
STAmount
balanceHook(AccountID const& account, AccountID const& issuer, STAmount const& amount)
balanceHookIOU(AccountID const& account, AccountID const& issuer, STAmount const& amount)
const override;
STAmount
balanceHookMPT(AccountID const& account, MPTIssue const& issue, std::int64_t amount)
const override;
STAmount
balanceHookSelfIssueMPT(MPTIssue const& issue, std::int64_t amount) const override;
void
creditHook(
creditHookIOU(
AccountID const& from,
AccountID const& to,
STAmount const& amount,
STAmount const& preCreditBalance) override;
void
creditHookMPT(
AccountID const& from,
AccountID const& to,
STAmount const& amount,
std::uint64_t preCreditBalanceHolder,
std::int64_t preCreditBalanceIssuer) override;
void
issuerSelfDebitHookMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance)
override;
void
adjustOwnerCountHook(AccountID const& account, std::uint32_t cur, std::uint32_t next) override;

View File

@@ -148,15 +148,35 @@ public:
// Accounts in a payment are not allowed to use assets acquired during that
// payment. The PaymentSandbox tracks the debits, credits, and owner count
// changes that accounts make during a payment. `balanceHook` adjusts
// changes that accounts make during a payment. `balanceHookIOU` adjusts
// balances so newly acquired assets are not counted toward the balance.
// This is required to support PaymentSandbox.
virtual STAmount
balanceHook(AccountID const& account, AccountID const& issuer, STAmount const& amount) const
balanceHookIOU(AccountID const& account, AccountID const& issuer, STAmount const& amount) const
{
XRPL_ASSERT(amount.holds<Issue>(), "balanceHookIOU: amount is for Issue");
return amount;
}
// balanceHookMPT adjusts balances so newly acquired assets are not counted
// toward the balance.
virtual STAmount
balanceHookMPT(AccountID const& account, MPTIssue const& issue, std::int64_t amount) const
{
return STAmount{issue, amount};
}
// An offer owned by an issuer and selling MPT is limited by the issuer's
// funds available to issue, which are originally available funds less
// already self sold MPT amounts (MPT sell offer). This hook is used
// by issuerFundsToSelfIssue() function.
virtual STAmount
balanceHookSelfIssueMPT(MPTIssue const& issue, std::int64_t amount) const
{
return STAmount{issue, amount};
}
// Accounts in a payment are not allowed to use assets acquired during that
// payment. The PaymentSandbox tracks the debits, credits, and owner count
// changes that accounts make during a payment. `ownerCountHook` adjusts the

View File

@@ -63,8 +63,8 @@ isVaultPseudoAccountFrozen(
isLPTokenFrozen(
ReadView const& view,
AccountID const& account,
Issue const& asset,
Issue const& asset2);
Asset const& asset,
Asset const& asset2);
// Return the list of enabled amendments
[[nodiscard]] std::set<uint256>

View File

@@ -1,8 +1,13 @@
#pragma once
#include <xrpl/basics/Expected.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/AmountConversions.h>
#include <xrpl/protocol/Feature.h>
@@ -11,6 +16,7 @@
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
namespace xrpl {
@@ -36,7 +42,7 @@ enum class IsDeposit : bool { No = false, Yes = true };
* @return LP Tokens as IOU
*/
STAmount
ammLPTokens(STAmount const& asset1, STAmount const& asset2, Issue const& lptIssue);
ammLPTokens(STAmount const& asset1, STAmount const& asset2, Asset const& lptIssue);
/** Calculate LP Tokens given asset's deposit amount.
* @param asset1Balance current AMM asset1 balance
@@ -124,7 +130,8 @@ withinRelativeDistance(Quality const& calcQuality, Quality const& reqQuality, Nu
template <typename Amt>
requires(
std::is_same_v<Amt, STAmount> || std::is_same_v<Amt, IOUAmount> ||
std::is_same_v<Amt, XRPAmount> || std::is_same_v<Amt, Number>)
std::is_same_v<Amt, XRPAmount> || std::is_same_v<Amt, MPTAmount> ||
std::is_same_v<Amt, Number>)
bool
withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist)
{
@@ -195,7 +202,7 @@ getAMMOfferStartWithTakerGets(
// Round downward to minimize the offer and to maximize the quality.
// This has the most impact when takerGets is XRP.
auto const takerGets =
toAmount<TOut>(getIssue(pool.out), nTakerGetsProposed, Number::downward);
toAmount<TOut>(getAsset(pool.out), nTakerGetsProposed, Number::downward);
return TAmounts<TIn, TOut>{swapAssetOut(pool, takerGets, tfee), takerGets};
};
@@ -262,7 +269,7 @@ getAMMOfferStartWithTakerPays(
// Round downward to minimize the offer and to maximize the quality.
// This has the most impact when takerPays is XRP.
auto const takerPays =
toAmount<TIn>(getIssue(pool.in), nTakerPaysProposed, Number::downward);
toAmount<TIn>(getAsset(pool.in), nTakerPaysProposed, Number::downward);
return TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
};
@@ -331,7 +338,7 @@ changeSpotPriceQuality(
<< " " << to_string(pool.out) << " " << quality << " " << tfee;
return std::nullopt;
}
auto const takerPays = toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
auto const takerPays = toAmount<TIn>(getAsset(pool.in), nTakerPays, Number::upward);
// should not fail
if (auto amounts = TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
Quality{amounts} < quality &&
@@ -360,7 +367,7 @@ changeSpotPriceQuality(
// Generate the offer starting with XRP side. Return seated offer amounts
// if the offer can be generated, otherwise nullopt.
auto amounts = [&]() {
if (isXRP(getIssue(pool.out)))
if (isXRP(getAsset(pool.out)))
return getAMMOfferStartWithTakerGets(pool, quality, tfee);
return getAMMOfferStartWithTakerPays(pool, quality, tfee);
}();
@@ -445,7 +452,7 @@ swapAssetIn(TAmounts<TIn, TOut> const& pool, TIn const& assetIn, std::uint16_t t
auto const denom = pool.in + assetIn * (1 - fee);
if (denom.signum() <= 0)
return toAmount<TOut>(getIssue(pool.out), 0);
return toAmount<TOut>(getAsset(pool.out), 0);
Number::setround(Number::upward);
auto const ratio = numerator / denom;
@@ -454,14 +461,14 @@ swapAssetIn(TAmounts<TIn, TOut> const& pool, TIn const& assetIn, std::uint16_t t
auto const swapOut = pool.out - ratio;
if (swapOut.signum() < 0)
return toAmount<TOut>(getIssue(pool.out), 0);
return toAmount<TOut>(getAsset(pool.out), 0);
return toAmount<TOut>(getIssue(pool.out), swapOut, Number::downward);
return toAmount<TOut>(getAsset(pool.out), swapOut, Number::downward);
}
else
{
return toAmount<TOut>(
getIssue(pool.out),
getAsset(pool.out),
pool.out - (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
Number::downward);
}
@@ -508,7 +515,7 @@ swapAssetOut(TAmounts<TIn, TOut> const& pool, TOut const& assetOut, std::uint16_
auto const denom = pool.out - assetOut;
if (denom.signum() <= 0)
{
return toMaxAmount<TIn>(getIssue(pool.in));
return toMaxAmount<TIn>(getAsset(pool.in));
}
Number::setround(Number::upward);
@@ -522,14 +529,14 @@ swapAssetOut(TAmounts<TIn, TOut> const& pool, TOut const& assetOut, std::uint16_
Number::setround(Number::upward);
auto const swapIn = numerator2 / feeMult;
if (swapIn.signum() < 0)
return toAmount<TIn>(getIssue(pool.in), 0);
return toAmount<TIn>(getAsset(pool.in), 0);
return toAmount<TIn>(getIssue(pool.in), swapIn, Number::upward);
return toAmount<TIn>(getAsset(pool.in), swapIn, Number::upward);
}
else
{
return toAmount<TIn>(
getIssue(pool.in),
getAsset(pool.in),
((pool.in * pool.out) / (pool.out - assetOut) - pool.in) / feeMult(tfee),
Number::upward);
}
@@ -616,9 +623,9 @@ getRoundedAsset(Rules const& rules, STAmount const& balance, A const& frac, IsDe
if (!rules.enabled(fixAMMv1_3))
{
if constexpr (std::is_same_v<A, STAmount>)
return multiply(balance, frac, balance.issue());
return multiply(balance, frac, balance.asset());
else
return toSTAmount(balance.issue(), balance * frac);
return toSTAmount(balance.asset(), balance * frac);
}
auto const rm = detail::getAssetRounding(isDeposit);
return multiply(balance, frac, rm);
@@ -712,4 +719,94 @@ adjustFracByTokens(
STAmount const& tokens,
Number const& frac);
/** Get AMM pool balances.
*/
std::pair<STAmount, STAmount>
ammPoolHolds(
ReadView const& view,
AccountID const& ammAccountID,
Asset const& asset1,
Asset const& asset2,
FreezeHandling freezeHandling,
AuthHandling authHandling,
beast::Journal const j);
/** Get AMM pool and LP token balances. If both optIssue are
* provided then they are used as the AMM token pair issues.
* Otherwise the missing issues are fetched from ammSle.
*/
Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
ammHolds(
ReadView const& view,
SLE const& ammSle,
std::optional<Asset> const& optAsset1,
std::optional<Asset> const& optAsset2,
FreezeHandling freezeHandling,
AuthHandling authHandling,
beast::Journal const j);
/** Get the balance of LP tokens.
*/
STAmount
ammLPHolds(
ReadView const& view,
Asset const& asset1,
Asset const& asset2,
AccountID const& ammAccount,
AccountID const& lpAccount,
beast::Journal const j);
STAmount
ammLPHolds(
ReadView const& view,
SLE const& ammSle,
AccountID const& lpAccount,
beast::Journal const j);
/** Get AMM trading fee for the given account. The fee is discounted
* if the account is the auction slot owner or one of the slot's authorized
* accounts.
*/
std::uint16_t
getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account);
/** Returns total amount held by AMM for the given token.
*/
STAmount
ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const& asset);
/** Delete trustlines to AMM. If all trustlines are deleted then
* AMM object and account are deleted. Otherwise tecINCOMPLETE is returned.
*/
TER
deleteAMMAccount(Sandbox& view, Asset const& asset, Asset const& asset2, beast::Journal j);
/** Initialize Auction and Voting slots and set the trading/discounted fee.
*/
void
initializeFeeAuctionVote(
ApplyView& view,
std::shared_ptr<SLE>& ammSle,
AccountID const& account,
Asset const& lptAsset,
std::uint16_t tfee);
/** Return true if the Liquidity Provider is the only AMM provider, false
* otherwise. Return tecINTERNAL if encountered an unexpected condition,
* for instance Liquidity Provider has more than one LPToken trustline.
*/
Expected<bool, TER>
isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount);
/** Due to rounding, the LPTokenBalance of the last LP might
* not match the LP's trustline balance. If it's within the tolerance,
* update LPTokenBalance to match the LP's trustline balance.
*/
Expected<bool, TER>
verifyAndAdjustLPTokenBalance(
Sandbox& sb,
STAmount const& lpTokens,
std::shared_ptr<SLE>& ammSle,
AccountID const& account);
} // namespace xrpl

View File

@@ -1,106 +0,0 @@
#pragma once
#include <xrpl/basics/Expected.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
namespace xrpl {
class ReadView;
class ApplyView;
class Sandbox;
class NetClock;
/** Get AMM pool balances.
*/
std::pair<STAmount, STAmount>
ammPoolHolds(
ReadView const& view,
AccountID const& ammAccountID,
Issue const& issue1,
Issue const& issue2,
FreezeHandling freezeHandling,
beast::Journal const j);
/** Get AMM pool and LP token balances. If both optIssue are
* provided then they are used as the AMM token pair issues.
* Otherwise the missing issues are fetched from ammSle.
*/
Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
ammHolds(
ReadView const& view,
SLE const& ammSle,
std::optional<Issue> const& optIssue1,
std::optional<Issue> const& optIssue2,
FreezeHandling freezeHandling,
beast::Journal const j);
/** Get the balance of LP tokens.
*/
STAmount
ammLPHolds(
ReadView const& view,
Currency const& cur1,
Currency const& cur2,
AccountID const& ammAccount,
AccountID const& lpAccount,
beast::Journal const j);
STAmount
ammLPHolds(
ReadView const& view,
SLE const& ammSle,
AccountID const& lpAccount,
beast::Journal const j);
/** Get AMM trading fee for the given account. The fee is discounted
* if the account is the auction slot owner or one of the slot's authorized
* accounts.
*/
std::uint16_t
getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account);
/** Returns total amount held by AMM for the given token.
*/
STAmount
ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Issue const& issue);
/** Delete trustlines to AMM. If all trustlines are deleted then
* AMM object and account are deleted. Otherwise tecIMPCOMPLETE is returned.
*/
TER
deleteAMMAccount(Sandbox& view, Issue const& asset, Issue const& asset2, beast::Journal j);
/** Initialize Auction and Voting slots and set the trading/discounted fee.
*/
void
initializeFeeAuctionVote(
ApplyView& view,
std::shared_ptr<SLE>& ammSle,
AccountID const& account,
Issue const& lptIssue,
std::uint16_t tfee);
/** Return true if the Liquidity Provider is the only AMM provider, false
* otherwise. Return tecINTERNAL if encountered an unexpected condition,
* for instance Liquidity Provider has more than one LPToken trustline.
*/
Expected<bool, TER>
isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount);
/** Due to rounding, the LPTokenBalance of the last LP might
* not match the LP's trustline balance. If it's within the tolerance,
* update LPTokenBalance to match the LP's trustline balance.
*/
Expected<bool, TER>
verifyAndAdjustLPTokenBalance(
Sandbox& sb,
STAmount const& lpTokens,
std::shared_ptr<SLE>& ammSle,
AccountID const& account);
} // namespace xrpl

View File

@@ -41,7 +41,8 @@ escrowUnlockApplyHelper<Issue>(
bool createAsset,
beast::Journal journal)
{
Keylet const trustLineKey = keylet::line(receiver, amount.issue());
Issue const& issue = amount.get<Issue>();
Keylet const trustLineKey = keylet::line(receiver, issue);
bool const recvLow = issuer > receiver;
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
@@ -64,9 +65,9 @@ escrowUnlockApplyHelper<Issue>(
return tecNO_LINE_INSUF_RESERVE;
}
Currency const currency = amount.getCurrency();
STAmount initialBalance(amount.issue());
initialBalance.setIssuer(noAccount());
Currency const currency = issue.currency;
STAmount initialBalance(issue);
initialBalance.get<Issue>().account = noAccount();
if (TER const ter = trustCreate(
view, // payment sandbox
@@ -113,7 +114,8 @@ escrowUnlockApplyHelper<Issue>(
if ((!senderIssuer && !receiverIssuer) && lockedRate != parityRate)
{
// compute transfer fee, if any
auto const xferFee = amount.value() - divideRound(amount, lockedRate, amount.issue(), true);
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.get<Issue>(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}

View File

@@ -80,6 +80,7 @@ authorizeMPToken(
* requireAuth check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
* WeakAuth intentionally allows missing MPTokens under MPToken V2.
*/
[[nodiscard]] TER
requireAuth(
@@ -114,6 +115,12 @@ canTransfer(
AccountID const& from,
AccountID const& to);
/** Check if Asset can be traded on DEX. return tecNO_PERMISSION
* if it doesn't and tesSUCCESS otherwise.
*/
[[nodiscard]] TER
canTrade(ReadView const& view, Asset const& asset);
//------------------------------------------------------------------------------
//
// Empty holding operations (MPT-specific)
@@ -164,4 +171,73 @@ createMPToken(
AccountID const& account,
std::uint32_t const flags);
TER
checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
beast::Journal j);
//------------------------------------------------------------------------------
//
// MPT Overflow related
//
//------------------------------------------------------------------------------
// MaximumAmount doesn't exceed 2**63-1
std::int64_t
maxMPTAmount(SLE const& sleIssuance);
// OutstandingAmount may overflow and available amount might be negative.
// But available amount is always <= |MaximumAmount - OutstandingAmount|.
std::int64_t
availableMPTAmount(SLE const& sleIssuance);
std::int64_t
availableMPTAmount(ReadView const& view, MPTID const& mptID);
/** Checks for two types of OutstandingAmount overflow during a send operation.
* 1. **Direct directSendNoFee (Overflow: No):** A true overflow check when
* `OutstandingAmount > MaximumAmount`. This threshold is used for direct
* directSendNoFee transactions that bypass the payment engine.
* 2. **accountSend & Payment Engine (Overflow: Yes):** A temporary overflow
* check when `OutstandingAmount > UINT64_MAX`. This higher threshold is used
* for `accountSend` and payments processed via the payment engine.
*/
bool
isMPTOverflow(
std::int64_t sendAmount,
std::uint64_t outstandingAmount,
std::int64_t maximumAmount,
AllowMPTOverflow allowOverflow);
/**
* Determine funds available for an issuer to sell in an issuer owned offer.
* Issuing step, which could be either MPTEndPointStep last step or BookStep's
* TakerPays may overflow OutstandingAmount. Redeeming step, in BookStep's
* TakerGets redeems the offer's owner funds, essentially balancing out
* the overflow, unless the offer's owner is the issuer.
*/
[[nodiscard]] STAmount
issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue);
/** Facilitate tracking of MPT sold by an issuer owning MPT sell offer.
* See ApplyView::issuerSelfDebitHookMPT().
*/
void
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount);
//------------------------------------------------------------------------------
//
// MPT DEX
//
//------------------------------------------------------------------------------
/* Return true if a transaction is allowed for the specified MPT/account. The
* function checks MPTokenIssuance and MPToken objects flags to determine if the
* transaction is allowed.
*/
TER
checkMPTTxAllowed(ReadView const& v, TxType tx, Asset const& asset, AccountID const& accountID);
} // namespace xrpl

View File

@@ -252,4 +252,14 @@ deleteAMMTrustLine(
std::optional<AccountID> const& ammAccountID,
beast::Journal j);
/** Delete AMMs MPToken. The passed `sle` must be obtained from a prior
* call to view.peek().
*/
[[nodiscard]] TER
deleteAMMMPToken(
ApplyView& view,
std::shared_ptr<SLE> sleMPT,
AccountID const& ammAccountID,
beast::Journal j);
} // namespace xrpl

View File

@@ -31,6 +31,9 @@ enum SpendableHandling { shSIMPLE_BALANCE, shFULL_BALANCE };
enum class WaiveTransferFee : bool { No = false, Yes };
/** Controls whether accountSend is allowed to overflow OutstandingAmount **/
enum class AllowMPTOverflow : bool { No = false, Yes };
/* Check if MPToken (for MPT) or trust line (for IOU) exists:
* - StrongAuth - before checking if authorization is required
* - WeakAuth
@@ -176,6 +179,16 @@ accountFunds(
FreezeHandling freezeHandling,
beast::Journal j);
// Overload with AuthHandling to support IOU and MPT.
[[nodiscard]] STAmount
accountFunds(
ReadView const& view,
AccountID const& id,
STAmount const& saDefault,
FreezeHandling freezeHandling,
AuthHandling authHandling,
beast::Journal j);
/** Returns the transfer fee as Rate based on the type of token
* @param view The ledger view
* @param amount The amount to transfer
@@ -257,7 +270,8 @@ accountSend(
AccountID const& to,
STAmount const& saAmount,
beast::Journal j,
WaiveTransferFee waiveFee = WaiveTransferFee::No);
WaiveTransferFee waiveFee = WaiveTransferFee::No,
AllowMPTOverflow allowOverflow = AllowMPTOverflow::No);
using MultiplePaymentDestinations = std::vector<std::pair<AccountID, Number>>;
/** Like accountSend, except one account is sending multiple payments (with the

View File

@@ -2,7 +2,7 @@
#include <xrpl/basics/Number.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
@@ -31,12 +31,12 @@ class Rules;
/** Calculate Liquidity Provider Token (LPT) Currency.
*/
Currency
ammLPTCurrency(Currency const& cur1, Currency const& cur2);
ammLPTCurrency(Asset const& asset1, Asset const& asset2);
/** Calculate LPT Issue from AMM asset pair.
*/
Issue
ammLPTIssue(Currency const& cur1, Currency const& cur2, AccountID const& ammAccountID);
ammLPTIssue(Asset const& asset1, Asset const& asset2, AccountID const& ammAccountID);
/** Validate the amount.
* If validZero is false and amount is beast::zero then invalid amount.
@@ -46,19 +46,19 @@ ammLPTIssue(Currency const& cur1, Currency const& cur2, AccountID const& ammAcco
NotTEC
invalidAMMAmount(
STAmount const& amount,
std::optional<std::pair<Issue, Issue>> const& pair = std::nullopt,
std::optional<std::pair<Asset, Asset>> const& pair = std::nullopt,
bool validZero = false);
NotTEC
invalidAMMAsset(
Issue const& issue,
std::optional<std::pair<Issue, Issue>> const& pair = std::nullopt);
Asset const& asset,
std::optional<std::pair<Asset, Asset>> const& pair = std::nullopt);
NotTEC
invalidAMMAssetPair(
Issue const& issue1,
Issue const& issue2,
std::optional<std::pair<Issue, Issue>> const& pair = std::nullopt);
Asset const& asset1,
Asset const& asset2,
std::optional<std::pair<Asset, Asset>> const& pair = std::nullopt);
/** Get time slot of the auction slot.
*/

View File

@@ -1,6 +1,7 @@
#pragma once
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/XRPAmount.h>
@@ -9,11 +10,12 @@
namespace xrpl {
inline STAmount
toSTAmount(IOUAmount const& iou, Issue const& iss)
toSTAmount(IOUAmount const& iou, Asset const& asset)
{
XRPL_ASSERT(asset.holds<Issue>(), "xrpl::toSTAmount : is Issue");
bool const isNeg = iou.signum() < 0;
std::uint64_t const umant = isNeg ? -iou.mantissa() : iou.mantissa();
return STAmount(iss, umant, iou.exponent(), isNeg, STAmount::unchecked());
return STAmount(asset, umant, iou.exponent(), isNeg, STAmount::unchecked());
}
inline STAmount
@@ -31,12 +33,25 @@ toSTAmount(XRPAmount const& xrp)
}
inline STAmount
toSTAmount(XRPAmount const& xrp, Issue const& iss)
toSTAmount(XRPAmount const& xrp, Asset const& asset)
{
XRPL_ASSERT(isXRP(iss.account) && isXRP(iss.currency), "xrpl::toSTAmount : is XRP");
XRPL_ASSERT(isXRP(asset), "xrpl::toSTAmount : is XRP");
return toSTAmount(xrp);
}
inline STAmount
toSTAmount(MPTAmount const& mpt)
{
return STAmount(mpt, noMPT());
}
inline STAmount
toSTAmount(MPTAmount const& mpt, Asset const& asset)
{
XRPL_ASSERT(asset.holds<MPTIssue>(), "xrpl::toSTAmount : is MPT");
return STAmount(mpt, asset.get<MPTIssue>());
}
template <class T>
T
toAmount(STAmount const& amt) = delete;
@@ -76,6 +91,21 @@ toAmount<XRPAmount>(STAmount const& amt)
return XRPAmount(sMant);
}
template <>
inline MPTAmount
toAmount<MPTAmount>(STAmount const& amt)
{
XRPL_ASSERT(
amt.holds<MPTIssue>() && amt.mantissa() <= maxMPTokenAmount && amt.exponent() == 0,
"xrpl::toAmount<MPTAmount> : maximum mantissa");
if (amt.mantissa() > maxMPTokenAmount || amt.exponent() != 0)
Throw<std::runtime_error>("toAmount<MPTAmount>: invalid mantissa or exponent");
bool const isNeg = amt.negative();
std::int64_t const sMant = isNeg ? -std::int64_t(amt.mantissa()) : amt.mantissa();
return MPTAmount(sMant);
}
template <class T>
T
toAmount(IOUAmount const& amt) = delete;
@@ -98,23 +128,36 @@ toAmount<XRPAmount>(XRPAmount const& amt)
return amt;
}
template <class T>
T
toAmount(MPTAmount const& amt) = delete;
template <>
inline MPTAmount
toAmount<MPTAmount>(MPTAmount const& amt)
{
return amt;
}
template <typename T>
T
toAmount(Issue const& issue, Number const& n, Number::rounding_mode mode = Number::getround())
toAmount(Asset const& asset, Number const& n, Number::rounding_mode mode = Number::getround())
{
saveNumberRoundMode const rm(Number::getround());
if (isXRP(issue))
if (isXRP(asset))
Number::setround(mode);
if constexpr (std::is_same_v<IOUAmount, T>)
return IOUAmount(n);
else if constexpr (std::is_same_v<XRPAmount, T>)
return XRPAmount(static_cast<std::int64_t>(n));
else if constexpr (std::is_same_v<MPTAmount, T>)
return MPTAmount(static_cast<std::int64_t>(n));
else if constexpr (std::is_same_v<STAmount, T>)
{
if (isXRP(issue))
return STAmount(issue, static_cast<std::int64_t>(n));
return STAmount(issue, n);
if (isXRP(asset))
return STAmount(asset, static_cast<std::int64_t>(n));
return STAmount(asset, n);
}
else
{
@@ -125,17 +168,23 @@ toAmount(Issue const& issue, Number const& n, Number::rounding_mode mode = Numbe
template <typename T>
T
toMaxAmount(Issue const& issue)
toMaxAmount(Asset const& asset)
{
if constexpr (std::is_same_v<IOUAmount, T>)
return IOUAmount(STAmount::cMaxValue, STAmount::cMaxOffset);
else if constexpr (std::is_same_v<XRPAmount, T>)
return XRPAmount(static_cast<std::int64_t>(STAmount::cMaxNativeN));
else if constexpr (std::is_same_v<MPTAmount, T>)
return MPTAmount(maxMPTokenAmount);
else if constexpr (std::is_same_v<STAmount, T>)
{
if (isXRP(issue))
return STAmount(issue, static_cast<std::int64_t>(STAmount::cMaxNativeN));
return STAmount(issue, STAmount::cMaxValue, STAmount::cMaxOffset);
return asset.visit(
[](Issue const& issue) {
if (isXRP(issue))
return STAmount(issue, static_cast<std::int64_t>(STAmount::cMaxNativeN));
return STAmount(issue, STAmount::cMaxValue, STAmount::cMaxOffset);
},
[](MPTIssue const& issue) { return STAmount(issue, maxMPTokenAmount); });
}
else
{
@@ -145,21 +194,23 @@ toMaxAmount(Issue const& issue)
}
inline STAmount
toSTAmount(Issue const& issue, Number const& n, Number::rounding_mode mode = Number::getround())
toSTAmount(Asset const& asset, Number const& n, Number::rounding_mode mode = Number::getround())
{
return toAmount<STAmount>(issue, n, mode);
return toAmount<STAmount>(asset, n, mode);
}
template <typename T>
Issue
getIssue(T const& amt)
Asset
getAsset(T const& amt)
{
if constexpr (std::is_same_v<IOUAmount, T>)
return noIssue();
else if constexpr (std::is_same_v<XRPAmount, T>)
return xrpIssue();
else if constexpr (std::is_same_v<MPTAmount, T>)
return noMPT();
else if constexpr (std::is_same_v<STAmount, T>)
return amt.issue();
return amt.asset();
else
{
constexpr bool alwaysFalse = !std::is_same_v<T, T>;
@@ -175,6 +226,8 @@ get(STAmount const& a)
return a.iou();
else if constexpr (std::is_same_v<XRPAmount, T>)
return a.xrp();
else if constexpr (std::is_same_v<MPTAmount, T>)
return a.mpt();
else if constexpr (std::is_same_v<STAmount, T>)
return a;
else

View File

@@ -2,20 +2,37 @@
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rules.h>
namespace xrpl {
class Asset;
class STAmount;
template <typename TIss>
concept ValidIssueType = std::is_same_v<TIss, Issue> || std::is_same_v<TIss, MPTIssue>;
template <typename T>
requires(
std::is_same_v<T, XRPAmount> || std::is_same_v<T, IOUAmount> ||
std::is_same_v<T, MPTAmount>)
struct AmountType
{
using amount_type = T;
};
template <typename A>
concept AssetType = std::is_convertible_v<A, Asset> || std::is_convertible_v<A, Issue> ||
std::is_convertible_v<A, MPTIssue> || std::is_convertible_v<A, MPTID>;
/* Used to check for an asset with either badCurrency()
* or MPT with 0 account.
*/
struct BadAsset
{
};
inline BadAsset const&
badAsset()
{
static BadAsset const a;
return a;
}
/* Asset is an abstraction of three different issue types: XRP, IOU, MPT.
* For historical reasons, two issue types XRP and IOU are wrapped in Issue
@@ -26,6 +43,9 @@ class Asset
{
public:
using value_type = std::variant<Issue, MPTIssue>;
using token_type = std::variant<Currency, MPTID>;
using AmtType =
std::variant<AmountType<XRPAmount>, AmountType<IOUAmount>, AmountType<MPTAmount>>;
private:
value_type issue_;
@@ -69,36 +89,42 @@ public:
constexpr value_type const&
value() const;
constexpr token_type
token() const;
void
setJson(Json::Value& jv) const;
STAmount
operator()(Number const&) const;
bool
constexpr AmtType
getAmountType() const;
// Custom, generic visit implementation
template <typename... Visitors>
constexpr auto
visit(Visitors&&... visitors) const -> decltype(auto)
{
// Simple delegation to the reusable utility, passing the internal
// variant data.
return detail::visit(issue_, std::forward<Visitors>(visitors)...);
}
constexpr bool
native() const
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
return issue.native();
if constexpr (std::is_same_v<TIss, MPTIssue>)
return false;
},
issue_);
return visit(
[&](Issue const& issue) { return issue.native(); },
[&](MPTIssue const&) { return false; });
}
bool
integral() const
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
return issue.native();
if constexpr (std::is_same_v<TIss, MPTIssue>)
return true;
},
issue_);
return visit(
[&](Issue const& issue) { return issue.native(); },
[&](MPTIssue const&) { return true; });
}
friend constexpr bool
@@ -110,6 +136,10 @@ public:
friend constexpr bool
operator==(Currency const& lhs, Asset const& rhs);
// rhs is either badCurrency() or MPT issuer is 0
friend constexpr bool
operator==(BadAsset const& lhs, Asset const& rhs);
/** Return true if both assets refer to the same currency (regardless of
* issuer) or MPT issuance. Otherwise return false.
*/
@@ -117,6 +147,12 @@ public:
equalTokens(Asset const& lhs, Asset const& rhs);
};
template <ValidIssueType TIss>
constexpr bool is_issue_v = std::is_same_v<TIss, Issue>;
template <ValidIssueType TIss>
constexpr bool is_mptissue_v = std::is_same_v<TIss, MPTIssue>;
inline Json::Value
to_json(Asset const& asset)
{
@@ -156,6 +192,29 @@ Asset::value() const
return issue_;
}
constexpr Asset::token_type
Asset::token() const
{
return visit(
[&](Issue const& issue) -> Asset::token_type { return issue.currency; },
[&](MPTIssue const& issue) -> Asset::token_type { return issue.getMptID(); });
}
constexpr Asset::AmtType
Asset::getAmountType() const
{
return visit(
[&](Issue const& issue) -> Asset::AmtType {
constexpr AmountType<XRPAmount> xrp;
constexpr AmountType<IOUAmount> iou;
return native() ? AmtType(xrp) : AmtType(iou);
},
[&](MPTIssue const& issue) -> Asset::AmtType {
constexpr AmountType<MPTAmount> mpt;
return AmtType(mpt);
});
}
constexpr bool
operator==(Asset const& lhs, Asset const& rhs)
{
@@ -177,7 +236,7 @@ operator<=>(Asset const& lhs, Asset const& rhs)
[]<ValidIssueType TLhs, ValidIssueType TRhs>(TLhs const& lhs_, TRhs const& rhs_) {
if constexpr (std::is_same_v<TLhs, TRhs>)
return std::weak_ordering(lhs_ <=> rhs_);
else if constexpr (std::is_same_v<TLhs, Issue> && std::is_same_v<TRhs, MPTIssue>)
else if constexpr (is_issue_v<TLhs> && is_mptissue_v<TRhs>)
return std::weak_ordering::greater;
else
return std::weak_ordering::less;
@@ -189,7 +248,17 @@ operator<=>(Asset const& lhs, Asset const& rhs)
constexpr bool
operator==(Currency const& lhs, Asset const& rhs)
{
return rhs.holds<Issue>() && rhs.get<Issue>().currency == lhs;
return rhs.visit(
[&](Issue const& issue) { return issue.currency == lhs; },
[](MPTIssue const& issue) { return false; });
}
constexpr bool
operator==(BadAsset const&, Asset const& rhs)
{
return rhs.visit(
[](Issue const& issue) -> bool { return badCurrency() == issue.currency; },
[](MPTIssue const& issue) -> bool { return issue.getIssuer() == xrpAccount(); });
}
constexpr bool
@@ -223,4 +292,36 @@ validJSONAsset(Json::Value const& jv);
Asset
assetFromJson(Json::Value const& jv);
Json::Value
to_json(Asset const& asset);
inline bool
isConsistent(Asset const& asset)
{
return asset.visit(
[](Issue const& issue) { return isConsistent(issue); },
[](MPTIssue const&) { return true; });
}
inline bool
validAsset(Asset const& asset)
{
return asset.visit(
[](Issue const& issue) { return isConsistent(issue) && issue.currency != badCurrency(); },
[](MPTIssue const& issue) { return issue.getIssuer() != xrpAccount(); });
}
template <class Hasher>
void
hash_append(Hasher& h, Asset const& r)
{
using beast::hash_append;
r.visit(
[&](Issue const& issue) { hash_append(h, issue); },
[&](MPTIssue const& issue) { hash_append(h, issue); });
}
std::ostream&
operator<<(std::ostream& os, Asset const& x);
} // namespace xrpl

View File

@@ -2,7 +2,7 @@
#include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Asset.h>
#include <boost/utility/base_from_member.hpp>
@@ -15,15 +15,15 @@ namespace xrpl {
class Book final : public CountedObject<Book>
{
public:
Issue in;
Issue out;
Asset in;
Asset out;
std::optional<uint256> domain;
Book()
{
}
Book(Issue const& in_, Issue const& out_, std::optional<uint256> const& domain_)
Book(Asset const& in_, Asset const& out_, std::optional<uint256> const& domain_)
: in(in_), out(out_), domain(domain_)
{
}
@@ -112,16 +112,67 @@ public:
}
};
template <>
struct hash<xrpl::MPTIssue> : private boost::base_from_member<std::hash<xrpl::MPTID>, 0>
{
private:
using id_hash_type = boost::base_from_member<std::hash<xrpl::MPTID>, 0>;
public:
explicit hash() = default;
using value_type = std::size_t;
using argument_type = xrpl::MPTIssue;
value_type
operator()(argument_type const& value) const
{
value_type const result(id_hash_type::member(value.getMptID()));
return result;
}
};
template <>
struct hash<xrpl::Asset>
{
private:
using value_type = std::size_t;
using argument_type = xrpl::Asset;
using issue_hasher = std::hash<xrpl::Issue>;
using mptissue_hasher = std::hash<xrpl::MPTIssue>;
issue_hasher m_issue_hasher;
mptissue_hasher m_mptissue_hasher;
public:
explicit hash() = default;
value_type
operator()(argument_type const& asset) const
{
return asset.visit(
[&](xrpl::Issue const& issue) {
value_type const result(m_issue_hasher(issue));
return result;
},
[&](xrpl::MPTIssue const& issue) {
value_type const result(m_mptissue_hasher(issue));
return result;
});
}
};
//------------------------------------------------------------------------------
template <>
struct hash<xrpl::Book>
{
private:
using issue_hasher = std::hash<xrpl::Issue>;
using asset_hasher = std::hash<xrpl::Asset>;
using uint256_hasher = xrpl::uint256::hasher;
issue_hasher m_issue_hasher;
asset_hasher m_asset_hasher;
uint256_hasher m_uint256_hasher;
public:
@@ -133,8 +184,8 @@ public:
value_type
operator()(argument_type const& value) const
{
value_type result(m_issue_hasher(value.in));
boost::hash_combine(result, m_issue_hasher(value.out));
value_type result(m_asset_hasher(value.in));
boost::hash_combine(result, m_asset_hasher(value.out));
if (value.domain)
boost::hash_combine(result, m_uint256_hasher(*value.domain));
@@ -159,6 +210,22 @@ struct hash<xrpl::Issue> : std::hash<xrpl::Issue>
// using Base::Base; // inherit ctors
};
template <>
struct hash<xrpl::MPTIssue> : std::hash<xrpl::MPTIssue>
{
explicit hash() = default;
using Base = std::hash<xrpl::MPTIssue>;
};
template <>
struct hash<xrpl::Asset> : std::hash<xrpl::Asset>
{
explicit hash() = default;
using Base = std::hash<xrpl::Asset>;
};
template <>
struct hash<xrpl::Book> : std::hash<xrpl::Book>
{

View File

@@ -0,0 +1,86 @@
#pragma once
#include <xrpl/protocol/UintTypes.h>
#include <type_traits>
namespace xrpl {
class STAmount;
class Asset;
class Issue;
class MPTIssue;
class IOUAmount;
class XRPAmount;
class MPTAmount;
template <typename A>
concept StepAmount =
std::is_same_v<A, XRPAmount> || std::is_same_v<A, IOUAmount> || std::is_same_v<A, MPTAmount>;
template <typename TIss>
concept ValidIssueType = std::is_same_v<TIss, Issue> || std::is_same_v<TIss, MPTIssue>;
template <typename A>
concept AssetType = std::is_convertible_v<A, Asset> || std::is_convertible_v<A, Issue> ||
std::is_convertible_v<A, MPTIssue> || std::is_convertible_v<A, MPTID>;
template <typename T>
concept ValidPathAsset = (std::is_same_v<T, Currency> || std::is_same_v<T, MPTID>);
template <class TTakerPays, class TTakerGets>
concept ValidTaker =
((std::is_same_v<TTakerPays, IOUAmount> || std::is_same_v<TTakerPays, XRPAmount> ||
std::is_same_v<TTakerPays, MPTAmount>) &&
(std::is_same_v<TTakerGets, IOUAmount> || std::is_same_v<TTakerGets, XRPAmount> ||
std::is_same_v<TTakerGets, MPTAmount>) &&
(!std::is_same_v<TTakerPays, XRPAmount> || !std::is_same_v<TTakerGets, XRPAmount>));
namespace detail {
// This template combines multiple callable objects (lambdas) into a single
// object that std::visit can use for overload resolution.
template <typename... Ts>
struct CombineVisitors : Ts...
{
// Bring all operator() overloads from base classes into this scope.
// It's the mechanism that makes the CombineVisitors struct function
// as a single callable object with multiple overloads.
using Ts::operator()...;
// Perfect forwarding constructor to correctly initialize the base class
// lambdas
constexpr CombineVisitors(Ts&&... ts) : Ts(std::forward<Ts>(ts))...
{
}
};
// This function forces function template argument deduction, which is more
// robust than class template argument deduction (CTAD) via the deduction guide.
template <typename... Ts>
constexpr CombineVisitors<std::decay_t<Ts>...>
make_combine_visitors(Ts&&... ts)
{
// std::decay_t<Ts> is used to remove references/constness from the lambda
// types before they are passed as template arguments to the CombineVisitors
// struct.
return CombineVisitors<std::decay_t<Ts>...>{std::forward<Ts>(ts)...};
}
// This function takes ANY variant and ANY number of visitors, and performs the
// visit. It is the reusable core logic.
template <typename Variant, typename... Visitors>
constexpr auto
visit(Variant&& v, Visitors&&... visitors) -> decltype(auto)
{
// Use the function template helper instead of raw CTAD.
auto visitor_set = make_combine_visitors(std::forward<Visitors>(visitors)...);
// Delegate to std::visit, perfectly forwarding the variant and the visitor
// set.
return std::visit(visitor_set, std::forward<Variant>(v));
}
} // namespace detail
} // namespace xrpl

View File

@@ -0,0 +1,460 @@
#pragma once
#include <xrpl/basics/Slice.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/detail/secp256k1.h>
#include <secp256k1_mpt.h>
namespace xrpl {
/**
* @brief Bundles an ElGamal public key with its associated encrypted amount.
*
* Used to represent a recipient in confidential transfers, containing both
* the recipient's ElGamal public key and the ciphertext encrypting the
* transfer amount under that key.
*/
struct ConfidentialRecipient
{
Slice publicKey; ///< The recipient's ElGamal public key (size=xrpl::ecPubKeyLength).
Slice encryptedAmount; ///< The encrypted amount ciphertext
///< (size=xrpl::ecGamalEncryptedTotalLength).
};
/// Holds two secp256k1 public key components representing an ElGamal ciphertext (C1, C2).
struct EcPair
{
secp256k1_pubkey c1;
secp256k1_pubkey c2;
};
/**
* @brief Increments the confidential balance version counter on an MPToken.
*
* The version counter is used to prevent replay attacks by binding proofs
* to a specific state of the account's confidential balance. Wraps to 0
* on overflow (defined behavior for unsigned integers).
*
* @param mptoken The MPToken ledger entry to update.
*/
inline void
incrementConfidentialVersion(STObject& mptoken)
{
// Retrieve current version and increment.
// Unsigned integer overflow is defined behavior in C++ (wraps to 0),
// which is acceptable here.
mptoken[sfConfidentialBalanceVersion] =
mptoken[~sfConfidentialBalanceVersion].value_or(0u) + 1u;
}
/**
* @brief Generates the context hash for ConfidentialMPTSend transactions.
*
* Creates a unique 256-bit hash that binds the zero-knowledge proofs to
* this specific send transaction, preventing proof reuse across transactions.
*
* @param account The sender's account ID.
* @param issuanceID The MPToken Issuance ID.
* @param sequence The transaction sequence number or ticket number.
* @param destination The destination account ID.
* @param version The sender's confidential balance version.
* @return A 256-bit context hash unique to this transaction.
*/
uint256
getSendContextHash(
AccountID const& account,
uint192 const& issuanceID,
std::uint32_t sequence,
AccountID const& destination,
std::uint32_t version);
/**
* @brief Generates the context hash for ConfidentialMPTClawback transactions.
*
* Creates a unique 256-bit hash that binds the equality proof to this
* specific clawback transaction.
*
* @param account The issuer's account ID.
* @param issuanceID The MPToken Issuance ID.
* @param sequence The transaction sequence number or ticket number.
* @param holder The holder's account ID being clawed back from.
* @return A 256-bit context hash unique to this transaction.
*/
uint256
getClawbackContextHash(
AccountID const& account,
uint192 const& issuanceID,
std::uint32_t sequence,
AccountID const& holder);
/**
* @brief Generates the context hash for ConfidentialMPTConvert transactions.
*
* Creates a unique 256-bit hash that binds the Schnorr proof (for key
* registration) to this specific convert transaction.
*
* @param account The holder's account ID.
* @param issuanceID The MPToken Issuance ID.
* @param sequence The transaction sequence number or a ticket number.
* @return A 256-bit context hash unique to this transaction.
*/
uint256
getConvertContextHash(AccountID const& account, uint192 const& issuanceID, std::uint32_t sequence);
/**
* @brief Generates the context hash for ConfidentialMPTConvertBack transactions.
*
* Creates a unique 256-bit hash that binds the zero-knowledge proofs to
* this specific convert-back transaction.
*
* @param account The holder's account ID.
* @param issuanceID The MPToken Issuance ID.
* @param sequence The transaction sequence number or a ticket number.
* @param version The holder's confidential balance version.
* @return A 256-bit context hash unique to this transaction.
*/
uint256
getConvertBackContextHash(
AccountID const& account,
uint192 const& issuanceID,
std::uint32_t sequence,
std::uint32_t version);
/**
* @brief Parses an ElGamal ciphertext into two secp256k1 public key components.
*
* Breaks a 66-byte encrypted amount (two 33-byte compressed EC points) into
* a pair containing (C1, C2) for use in cryptographic operations.
*
* @param buffer The 66-byte buffer containing the compressed ciphertext.
* @return The parsed pair (c1, c2) if successful, std::nullopt if the buffer is invalid.
*/
std::optional<EcPair>
makeEcPair(Slice const& buffer);
/**
* @brief Serializes an EcPair into compressed form.
*
* Converts an EcPair (C1, C2) back into a 66-byte buffer containing
* two 33-byte compressed EC points.
*
* @param pair The EcPair to serialize.
* @return The 66-byte buffer, or std::nullopt if serialization fails.
*/
std::optional<Buffer>
serializeEcPair(EcPair const& pair);
/**
* @brief Verifies that a buffer contains two valid, parsable EC public keys.
*
* @param buffer The input buffer containing two concatenated components.
* @return true if both components can be parsed successfully, false otherwise.
*/
bool
isValidCiphertext(Slice const& buffer);
/**
* @brief Verifies that a buffer contains a valid, parsable compressed EC point.
*
* Can be used to validate both compressed public keys and Pedersen commitments.
* Fails early if the prefix byte is not 0x02 or 0x03.
*
* @param buffer The input buffer containing a compressed EC point (33 bytes).
* @return true if the point can be parsed successfully, false otherwise.
*/
bool
isValidCompressedECPoint(Slice const& buffer);
/**
* @brief Homomorphically adds two ElGamal ciphertexts.
*
* Uses the additive homomorphic property of ElGamal encryption to compute
* Enc(a + b) from Enc(a) and Enc(b) without decryption.
*
* @param a The first ciphertext (66 bytes).
* @param b The second ciphertext (66 bytes).
* @return The resulting ciphertext Enc(a + b), or std::nullopt on failure.
*/
std::optional<Buffer>
homomorphicAdd(Slice const& a, Slice const& b);
/**
* @brief Homomorphically subtracts two ElGamal ciphertexts.
*
* Uses the additive homomorphic property of ElGamal encryption to compute
* Enc(a - b) from Enc(a) and Enc(b) without decryption.
*
* @param a The minuend ciphertext (66 bytes).
* @param b The subtrahend ciphertext (66 bytes).
* @return The resulting ciphertext Enc(a - b), or std::nullopt on failure.
*/
std::optional<Buffer>
homomorphicSubtract(Slice const& a, Slice const& b);
/**
* @brief Encrypts an amount using ElGamal encryption.
*
* Produces a ciphertext C = (C1, C2) where C1 = r*G and C2 = m*G + r*Pk,
* using the provided blinding factor r.
*
* @param amt The plaintext amount to encrypt.
* @param pubKeySlice The recipient's ElGamal public key (size=xrpl::ecPubKeyLength).
* @param blindingFactor The randomness used as blinding factor r
* (size=xrpl::ecBlindingFactorLength).
* @return The ciphertext (size=xrpl::ecGamalEncryptedTotalLength), or std::nullopt on failure.
*/
std::optional<Buffer>
encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindingFactor);
/**
* @brief Generates the canonical zero encryption for a specific MPToken.
*
* Creates a deterministic encryption of zero that is unique to the account
* and MPT issuance. Used to initialize confidential balance fields.
*
* @param pubKeySlice The holder's ElGamal public key (size=xrpl::ecPubKeyLength).
* @param account The account ID of the token holder.
* @param mptId The MPToken Issuance ID.
* @return The canonical zero ciphertext (size=xrpl::ecGamalEncryptedTotalLength), or std::nullopt
* on failure.
*/
std::optional<Buffer>
encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, MPTID const& mptId);
/**
* @brief Verifies a Schnorr proof of knowledge of an ElGamal private key.
*
* Proves that the submitter knows the secret key corresponding to the
* provided public key, without revealing the secret key itself.
*
* @param pubKeySlice The ElGamal public key (size=xrpl::ecPubKeyLength).
* @param proofSlice The Schnorr proof (size=xrpl::ecSchnorrProofLength).
* @param contextHash The 256-bit context hash binding the proof.
* @return tesSUCCESS if valid, or an error code otherwise.
*/
TER
verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash);
/**
* @brief Validates the format of encrypted amount fields in a transaction.
*
* Checks that all ciphertext fields in the transaction object have the
* correct length and contain valid EC points. This function is only used
* by ConfidentialMPTConvert and ConfidentialMPTConvertBack transactions.
*
* @param object The transaction object containing encrypted amount fields.
* @return tesSUCCESS if all formats are valid, temMALFORMED if required fields
* are missing, or temBAD_CIPHERTEXT if format validation fails.
*/
NotTEC
checkEncryptedAmountFormat(STObject const& object);
/**
* @brief Verifies revealed amount encryptions for all recipients.
*
* Validates that the same amount was correctly encrypted for the holder,
* issuer, and optionally the auditor using their respective public keys.
*
* @param amount The revealed plaintext amount.
* @param blindingFactor The blinding factor used in all encryptions
* (size=xrpl::ecBlindingFactorLength).
* @param holder The holder's public key and encrypted amount.
* @param issuer The issuer's public key and encrypted amount.
* @param auditor Optional auditor's public key and encrypted amount.
* @return tesSUCCESS if all encryptions are valid, or an error code otherwise.
*/
TER
verifyRevealedAmount(
uint64_t const amount,
Slice const& blindingFactor,
ConfidentialRecipient const& holder,
ConfidentialRecipient const& issuer,
std::optional<ConfidentialRecipient> const& auditor);
/**
* @brief Returns the number of recipients in a confidential transfer.
*
* Returns 4 if an auditor is present (sender, destination, issuer, auditor),
* or 3 if no auditor (sender, destination, issuer).
*
* @param hasAuditor Whether the issuance has an auditor configured.
* @return The number of recipients (3 or 4).
*/
constexpr std::size_t
getConfidentialRecipientCount(bool hasAuditor)
{
return hasAuditor ? 4 : 3;
}
/**
* @brief Returns the size of a multi-ciphertext equality proof.
*
* Computes the byte size required for a zero-knowledge proof that demonstrates
* multiple ciphertexts encrypt the same plaintext value. The size depends on
* the number of recipients.
*
* @param nRecipients The number of recipients (typically 3 or 4).
* @return The proof size in bytes.
*/
inline std::size_t
getEqualityProofSize(std::size_t nRecipients)
{
return secp256k1_mpt_proof_equality_shared_r_size(nRecipients);
}
/**
* @brief Verifies a clawback equality proof.
*
* Proves that the issuer knows the exact amount encrypted in the holder's
* balance ciphertext. Used in ConfidentialMPTClawback to verify the issuer
* can decrypt the balance using their private key.
*
* @param amount The revealed plaintext amount.
* @param proof The zero-knowledge proof bytes.
* @param pubKeySlice The issuer's ElGamal public key (64 bytes).
* @param ciphertext The issuer's encrypted balance on the holder's account (66 bytes).
* @param contextHash The 256-bit context hash binding the proof.
* @return tesSUCCESS if the proof is valid, or an error code otherwise.
*/
TER
verifyClawbackEqualityProof(
uint64_t const amount,
Slice const& proof,
Slice const& pubKeySlice,
Slice const& ciphertext,
uint256 const& contextHash);
/**
* @brief Generates a cryptographically secure 32-byte blinding factor.
*
* Produces random bytes suitable for use as an ElGamal blinding factor
* or Pedersen commitment randomness.
*
* @return A 32-byte buffer containing the random blinding factor.
*/
Buffer
generateBlindingFactor();
/**
* @brief Verifies all zero-knowledge proofs for a ConfidentialMPTSend transaction.
*
* This function calls mpt_verify_send_proof API in the mpt-crypto utility lib, which verifies the
* equality proof, amount linkage, balance linkage, and range proof.
* Equality proof: Proves the same value is encrypted for the sender, receiver, issuer, and auditor.
* Amount linkage: Proves the send amount matches the amount Pedersen commitment.
* Balance linkage: Proves the sender's balance matches the balance Pedersen
* commitment.
* Range proof: Proves the amount and the remaining balance are within range [0, 2^64-1].
*
* @param proof The full proof blob.
* @param sender The sender's public key and encrypted amount.
* @param destination The destination's public key and encrypted amount.
* @param issuer The issuer's public key and encrypted amount.
* @param auditor The auditor's public key and encrypted amount if present.
* @param spendingBalance The sender's current spending balance ciphertext.
* @param amountCommitment The Pedersen commitment to the send amount.
* @param balanceCommitment The Pedersen commitment to the sender's balance.
* @param contextHash The context hash binding the proof.
* @return tesSUCCESS if all proofs are valid, or an error code otherwise.
*/
TER
verifySendProof(
Slice const& proof,
ConfidentialRecipient const& sender,
ConfidentialRecipient const& destination,
ConfidentialRecipient const& issuer,
std::optional<ConfidentialRecipient> const& auditor,
Slice const& spendingBalance,
Slice const& amountCommitment,
Slice const& balanceCommitment,
uint256 const& contextHash);
/**
* @brief Verifies all zero-knowledge proofs for a ConfidentialMPTConvertBack transaction.
*
* This function calls mpt_verify_convert_back_proof API in the mpt-crypto utility lib, which
* verifies the balance linkage proof and range proof. Balance linkage proof: proves the balance
* commitment matches the spending ciphertext. Range proof: proves the remaining balance after
* convert back is within range [0, 2^64-1].
*
* @param proof The full proof blob.
* @param pubKeySlice The holder's public key.
* @param spendingBalance The holder's spending balance ciphertext.
* @param balanceCommitment The Pedersen commitment to the balance.
* @param amount The amount being converted back to public.
* @param contextHash The context hash binding the proof.
* @return tesSUCCESS if all proofs are valid, or an error code otherwise.
*/
TER
verifyConvertBackProof(
Slice const& proof,
Slice const& pubKeySlice,
Slice const& spendingBalance,
Slice const& balanceCommitment,
uint64_t amount,
uint256 const& contextHash);
/**
* @brief Sequential reader for extracting proof components from a ZKProof blob.
*
* Encapsulates the offset-based arithmetic for slicing a concatenated proof
* blob into its individual components (equality proofs, Pedersen linkage
* proofs, bulletproofs, etc.). Performs bounds checking on every read and
* tracks whether the entire blob has been consumed.
*
* Usage:
* @code
* ProofReader reader(tx[sfZKProof]);
* auto equalityProof = reader.read(sizeEquality);
* auto pedersenProof = reader.read(ecPedersenProofLength);
* if (!equalityProof || !pedersenProof || !reader.done())
* return tecINTERNAL;
* @endcode
*/
class ProofReader
{
Slice data_;
std::size_t offset_ = 0;
public:
explicit ProofReader(Slice data) : data_(data)
{
}
/**
* @brief Read the next @p length bytes from the proof blob.
*
* @param length Number of bytes to read.
* @return A Slice of the requested bytes, or std::nullopt if there are
* not enough remaining bytes.
*/
[[nodiscard]] std::optional<Slice>
read(std::size_t length)
{
if (offset_ + length > data_.size())
return std::nullopt;
auto result = data_.substr(offset_, length);
offset_ += length;
return result;
}
/**
* @brief Returns true when every byte has been consumed.
*/
[[nodiscard]] bool
done() const
{
return offset_ == data_.size();
}
};
} // namespace xrpl

View File

@@ -173,7 +173,8 @@ enum LedgerEntryType : std::uint16_t {
LSF_FLAG(lsfMPTCanEscrow, 0x00000008) \
LSF_FLAG(lsfMPTCanTrade, 0x00000010) \
LSF_FLAG(lsfMPTCanTransfer, 0x00000020) \
LSF_FLAG(lsfMPTCanClawback, 0x00000040)) \
LSF_FLAG(lsfMPTCanClawback, 0x00000040) \
LSF_FLAG(lsfMPTCanConfidentialAmount, 0x00000080)) \
\
LEDGER_OBJECT(MPTokenIssuanceMutable, \
LSF_FLAG(lsmfMPTCanMutateCanLock, 0x00000002) \
@@ -183,11 +184,13 @@ enum LedgerEntryType : std::uint16_t {
LSF_FLAG(lsmfMPTCanMutateCanTransfer, 0x00000020) \
LSF_FLAG(lsmfMPTCanMutateCanClawback, 0x00000040) \
LSF_FLAG(lsmfMPTCanMutateMetadata, 0x00010000) \
LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000)) \
LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000) \
LSF_FLAG(lsmfMPTCannotMutateCanConfidentialAmount, 0x00040000)) \
\
LEDGER_OBJECT(MPToken, \
LSF_FLAG2(lsfMPTLocked, 0x00000001) \
LSF_FLAG(lsfMPTAuthorized, 0x00000002)) \
LSF_FLAG(lsfMPTAuthorized, 0x00000002) \
LSF_FLAG(lsfMPTAMM, 0x00000004)) \
\
LEDGER_OBJECT(Credential, \
LSF_FLAG(lsfAccepted, 0x00010000)) \

View File

@@ -22,11 +22,12 @@ public:
using value_type = std::int64_t;
protected:
value_type value_;
value_type value_{};
public:
MPTAmount() = default;
constexpr MPTAmount(MPTAmount const& other) = default;
constexpr MPTAmount(beast::Zero);
constexpr MPTAmount&
operator=(MPTAmount const& other) = default;
@@ -85,6 +86,11 @@ constexpr MPTAmount::MPTAmount(value_type value) : value_(value)
{
}
constexpr MPTAmount::MPTAmount(beast::Zero)
{
*this = beast::zero;
}
constexpr MPTAmount&
MPTAmount::operator=(beast::Zero)
{
@@ -116,6 +122,14 @@ MPTAmount::value() const
return value_;
}
// Output MPTAmount as just the value.
template <class Char, class Traits>
std::basic_ostream<Char, Traits>&
operator<<(std::basic_ostream<Char, Traits>& os, MPTAmount const& q)
{
return os << q.value();
}
inline std::string
to_string(MPTAmount const& amount)
{

View File

@@ -17,7 +17,14 @@ private:
public:
MPTIssue() = default;
explicit MPTIssue(MPTID const& issuanceID);
MPTIssue(MPTID const& issuanceID);
MPTIssue(std::uint32_t sequence, AccountID const& account);
operator MPTID const&() const
{
return mptID_;
}
AccountID const&
getIssuer() const;
@@ -73,6 +80,47 @@ isXRP(MPTID const&)
return false;
}
inline AccountID
getMPTIssuer(MPTID const& mptid)
{
static_assert(sizeof(MPTID) == (sizeof(std::uint32_t) + sizeof(AccountID)));
// Extract the 20 bytes for the AccountID
std::array<std::uint8_t, sizeof(AccountID)> bytes{};
std::copy_n(mptid.data() + sizeof(std::uint32_t), sizeof(AccountID), bytes.begin());
// bit_cast is a "magic" compiler intrinsic that is
// usually optimized away to nothing in the final assembly.
return std::bit_cast<AccountID>(bytes);
}
// Disallow temporary
inline AccountID const&
getMPTIssuer(MPTID const&&) = delete;
inline AccountID const&
getMPTIssuer(MPTID&&) = delete;
inline MPTID
noMPT()
{
static MPTIssue const mpt{0, noAccount()};
return mpt.getMptID();
}
inline MPTID
badMPT()
{
static MPTIssue const mpt{0, xrpAccount()};
return mpt.getMptID();
}
template <class Hasher>
void
hash_append(Hasher& h, MPTIssue const& r)
{
using beast::hash_append;
hash_append(h, r.getMptID());
}
Json::Value
to_json(MPTIssue const& mptIssue);
@@ -82,4 +130,17 @@ to_string(MPTIssue const& mptIssue);
MPTIssue
mptIssueFromJson(Json::Value const& jv);
std::ostream&
operator<<(std::ostream& os, MPTIssue const& x);
} // namespace xrpl
namespace std {
template <>
struct hash<xrpl::MPTID> : xrpl::MPTID::hasher
{
explicit hash() = default;
};
} // namespace std

View File

@@ -0,0 +1,130 @@
#pragma once
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Concepts.h>
namespace xrpl {
/* Represent STPathElement's asset, which can be Currency or MPTID.
*/
class PathAsset
{
private:
std::variant<Currency, MPTID> easset_;
public:
PathAsset() = default;
// Enables comparing Asset and PathAsset
PathAsset(Asset const& asset);
PathAsset(Currency const& currency) : easset_(currency)
{
}
PathAsset(MPTID const& mpt) : easset_(mpt)
{
}
template <ValidPathAsset T>
constexpr bool
holds() const;
constexpr bool
isXRP() const;
template <ValidPathAsset T>
T const&
get() const;
constexpr std::variant<Currency, MPTID> const&
value() const;
// Custom, generic visit implementation
template <typename... Visitors>
constexpr auto
visit(Visitors&&... visitors) const -> decltype(auto)
{
// Simple delegation to the reusable utility, passing the internal
// variant data.
return detail::visit(easset_, std::forward<Visitors>(visitors)...);
}
friend constexpr bool
operator==(PathAsset const& lhs, PathAsset const& rhs);
};
template <ValidPathAsset PA>
constexpr bool is_currency_v = std::is_same_v<PA, Currency>;
template <ValidPathAsset PA>
constexpr bool is_mptid_v = std::is_same_v<PA, MPTID>;
inline PathAsset::PathAsset(Asset const& asset)
{
asset.visit(
[&](Issue const& issue) { easset_ = issue.currency; },
[&](MPTIssue const& issue) { easset_ = issue.getMptID(); });
}
template <ValidPathAsset T>
constexpr bool
PathAsset::holds() const
{
return std::holds_alternative<T>(easset_);
}
template <ValidPathAsset T>
T const&
PathAsset::get() const
{
if (!holds<T>())
Throw<std::runtime_error>("PathAsset doesn't hold requested asset.");
return std::get<T>(easset_);
}
constexpr std::variant<Currency, MPTID> const&
PathAsset::value() const
{
return easset_;
}
constexpr bool
PathAsset::isXRP() const
{
return visit(
[&](Currency const& currency) { return xrpl::isXRP(currency); },
[](MPTID const&) { return false; });
}
constexpr bool
operator==(PathAsset const& lhs, PathAsset const& rhs)
{
return std::visit(
[]<ValidPathAsset TLhs, ValidPathAsset TRhs>(TLhs const& lhs_, TRhs const& rhs_) {
if constexpr (std::is_same_v<TLhs, TRhs>)
return lhs_ == rhs_;
else
return false;
},
lhs.value(),
rhs.value());
}
template <typename Hasher>
void
hash_append(Hasher& h, PathAsset const& pathAsset)
{
std::visit([&]<ValidPathAsset T>(T const& e) { hash_append(h, e); }, pathAsset.value());
}
inline bool
isXRP(PathAsset const& asset)
{
return asset.isXRP();
}
std::string
to_string(PathAsset const& asset);
std::ostream&
operator<<(std::ostream& os, PathAsset const& x);
} // namespace xrpl

View File

@@ -307,4 +307,46 @@ std::size_t constexpr permissionMaxSize = 10;
/** The maximum number of transactions that can be in a batch. */
std::size_t constexpr maxBatchTxCount = 8;
/** Length of one component of EC ElGamal ciphertext */
std::size_t constexpr ecGamalEncryptedLength = 33;
/** EC ElGamal ciphertext length: two 33-byte components concatenated */
std::size_t constexpr ecGamalEncryptedTotalLength = ecGamalEncryptedLength * 2;
/** Length of equality ZKProof in bytes */
std::size_t constexpr ecEqualityProofLength = 98;
/** Length of EC point (compressed) */
std::size_t constexpr compressedECPointLength = 33;
/** Length of EC public key (compressed) */
std::size_t constexpr ecPubKeyLength = compressedECPointLength;
/** Length of EC private key in bytes */
std::size_t constexpr ecPrivKeyLength = 32;
/** Length of the EC blinding factor in bytes */
std::size_t constexpr ecBlindingFactorLength = 32;
/** Length of Schnorr ZKProof for public key registration in bytes */
std::size_t constexpr ecSchnorrProofLength = 65;
/** Length of ElGamal Pedersen linkage proof in bytes */
std::size_t constexpr ecPedersenProofLength = 195;
/** Length of Pedersen Commitment (compressed) */
std::size_t constexpr ecPedersenCommitmentLength = compressedECPointLength;
/** Length of single bulletproof (range proof for 1 commitment) in bytes */
std::size_t constexpr ecSingleBulletproofLength = 688;
/** Length of double bulletproof (range proof for 2 commitments) in bytes */
std::size_t constexpr ecDoubleBulletproofLength = 754;
/** Compressed EC point prefix for even y-coordinate */
std::uint8_t constexpr ecCompressedPrefixEvenY = 0x02;
/** Compressed EC point prefix for odd y-coordinate */
std::uint8_t constexpr ecCompressedPrefixOddY = 0x03;
} // namespace xrpl

View File

@@ -164,12 +164,9 @@ public:
constexpr TIss const&
get() const;
Issue const&
issue() const;
// These three are deprecated
Currency const&
getCurrency() const;
template <ValidIssueType TIss>
TIss&
get();
AccountID const&
getIssuer() const;
@@ -225,9 +222,6 @@ public:
void
clear(Asset const& asset);
void
setIssuer(AccountID const& uIssuer);
/** Set the Issue for this amount. */
void
setIssue(Asset const& asset);
@@ -466,16 +460,11 @@ STAmount::get() const
return mAsset.get<TIss>();
}
inline Issue const&
STAmount::issue() const
template <ValidIssueType TIss>
TIss&
STAmount::get()
{
return get<Issue>();
}
inline Currency const&
STAmount::getCurrency() const
{
return mAsset.get<Issue>().currency;
return mAsset.get<TIss>();
}
inline AccountID const&
@@ -505,11 +494,13 @@ operator bool() const noexcept
inline STAmount::
operator Number() const
{
if (native())
return xrp();
if (mAsset.holds<MPTIssue>())
return mpt();
return iou();
return asset().visit(
[&](Issue const& issue) -> Number {
if (issue.native())
return xrp();
return iou();
},
[&](MPTIssue const&) -> Number { return mpt(); });
}
inline STAmount&
@@ -568,12 +559,6 @@ STAmount::clear(Asset const& asset)
clear();
}
inline void
STAmount::setIssuer(AccountID const& uIssuer)
{
mAsset.get<Issue>().account = uIssuer;
}
inline STAmount const&
STAmount::value() const noexcept
{

View File

@@ -349,6 +349,8 @@ public:
void
setFieldH128(SField const& field, uint128 const&);
void
setFieldH192(SField const& field, uint192 const&);
void
setFieldH256(SField const& field, uint256 const&);
void
setFieldI32(SField const& field, std::int32_t);

View File

@@ -3,6 +3,8 @@
#include <xrpl/basics/CountedObject.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/PathAsset.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/UintTypes.h>
@@ -16,7 +18,7 @@ class STPathElement final : public CountedObject<STPathElement>
{
unsigned int mType;
AccountID mAccountID;
Currency mCurrencyID;
PathAsset mAssetID;
AccountID mIssuerID;
bool is_offer_;
@@ -28,8 +30,10 @@ public:
typeAccount = 0x01, // Rippling through an account (vs taking an offer).
typeCurrency = 0x10, // Currency follows.
typeIssuer = 0x20, // Issuer follows.
typeMPT = 0x40, // MPT follows.
typeBoundary = 0xFF, // Boundary between alternate paths.
typeAll = typeAccount | typeCurrency | typeIssuer,
typeAsset = typeCurrency | typeMPT,
typeAll = typeAccount | typeCurrency | typeIssuer | typeMPT,
// Combination of all types.
};
@@ -40,19 +44,19 @@ public:
STPathElement(
std::optional<AccountID> const& account,
std::optional<Currency> const& currency,
std::optional<PathAsset> const& asset,
std::optional<AccountID> const& issuer);
STPathElement(
AccountID const& account,
Currency const& currency,
PathAsset const& asset,
AccountID const& issuer,
bool forceCurrency = false);
bool forceAsset = false);
STPathElement(
unsigned int uType,
AccountID const& account,
Currency const& currency,
PathAsset const& asset,
AccountID const& issuer);
auto
@@ -70,6 +74,12 @@ public:
bool
hasCurrency() const;
bool
hasMPT() const;
bool
hasAsset() const;
bool
isNone() const;
@@ -78,12 +88,21 @@ public:
AccountID const&
getAccountID() const;
PathAsset const&
getPathAsset() const;
Currency const&
getCurrency() const;
MPTID const&
getMPTID() const;
AccountID const&
getIssuerID() const;
bool
isType(Type const& pe) const;
bool
operator==(STPathElement const& t) const;
@@ -118,7 +137,7 @@ public:
emplace_back(Args&&... args);
bool
hasSeen(AccountID const& account, Currency const& currency, AccountID const& issuer) const;
hasSeen(AccountID const& account, PathAsset const& asset, AccountID const& issuer) const;
Json::Value getJson(JsonOptions) const;
@@ -221,7 +240,7 @@ inline STPathElement::STPathElement() : mType(typeNone), is_offer_(true)
inline STPathElement::STPathElement(
std::optional<AccountID> const& account,
std::optional<Currency> const& currency,
std::optional<PathAsset> const& asset,
std::optional<AccountID> const& issuer)
: mType(typeNone)
{
@@ -238,10 +257,10 @@ inline STPathElement::STPathElement(
mAccountID != noAccount(), "xrpl::STPathElement::STPathElement : account is set");
}
if (currency)
if (asset)
{
mCurrencyID = *currency;
mType |= typeCurrency;
mAssetID = *asset;
mType |= mAssetID.holds<Currency>() ? typeCurrency : typeMPT;
}
if (issuer)
@@ -256,20 +275,20 @@ inline STPathElement::STPathElement(
inline STPathElement::STPathElement(
AccountID const& account,
Currency const& currency,
PathAsset const& asset,
AccountID const& issuer,
bool forceCurrency)
bool forceAsset)
: mType(typeNone)
, mAccountID(account)
, mCurrencyID(currency)
, mAssetID(asset)
, mIssuerID(issuer)
, is_offer_(isXRP(mAccountID))
{
if (!is_offer_)
mType |= typeAccount;
if (forceCurrency || !isXRP(currency))
mType |= typeCurrency;
if (forceAsset || !isXRP(mAssetID))
mType |= asset.holds<Currency>() ? typeCurrency : typeMPT;
if (!isXRP(issuer))
mType |= typeIssuer;
@@ -280,14 +299,19 @@ inline STPathElement::STPathElement(
inline STPathElement::STPathElement(
unsigned int uType,
AccountID const& account,
Currency const& currency,
PathAsset const& asset,
AccountID const& issuer)
: mType(uType)
, mAccountID(account)
, mCurrencyID(currency)
, mAssetID(asset)
, mIssuerID(issuer)
, is_offer_(isXRP(mAccountID))
{
// uType could be assetType; i.e. either Currency or MPTID.
// Get the actual type.
mAssetID.visit(
[&](Currency const&) { mType = mType & (~Type::typeMPT); },
[&](MPTID const&) { mType = mType & (~Type::typeCurrency); });
hash_value_ = get_hash(*this);
}
@@ -309,16 +333,34 @@ STPathElement::isAccount() const
return !isOffer();
}
inline bool
STPathElement::isType(Type const& pe) const
{
return (mType & pe) != 0u;
}
inline bool
STPathElement::hasIssuer() const
{
return getNodeType() & STPathElement::typeIssuer;
return isType(STPathElement::typeIssuer);
}
inline bool
STPathElement::hasCurrency() const
{
return getNodeType() & STPathElement::typeCurrency;
return isType(STPathElement::typeCurrency);
}
inline bool
STPathElement::hasMPT() const
{
return isType(STPathElement::typeMPT);
}
inline bool
STPathElement::hasAsset() const
{
return isType(STPathElement::typeAsset);
}
inline bool
@@ -335,10 +377,22 @@ STPathElement::getAccountID() const
return mAccountID;
}
inline PathAsset const&
STPathElement::getPathAsset() const
{
return mAssetID;
}
inline Currency const&
STPathElement::getCurrency() const
{
return mCurrencyID;
return mAssetID.get<Currency>();
}
inline MPTID const&
STPathElement::getMPTID() const
{
return mAssetID.get<MPTID>();
}
inline AccountID const&
@@ -351,7 +405,7 @@ inline bool
STPathElement::operator==(STPathElement const& t) const
{
return (mType & typeAccount) == (t.mType & typeAccount) && hash_value_ == t.hash_value_ &&
mAccountID == t.mAccountID && mCurrencyID == t.mCurrencyID && mIssuerID == t.mIssuerID;
mAccountID == t.mAccountID && mAssetID == t.mAssetID && mIssuerID == t.mIssuerID;
}
inline bool

View File

@@ -121,6 +121,8 @@ enum TEMcodes : TERUnderlyingType {
temARRAY_TOO_LARGE,
temBAD_TRANSFER_FEE,
temINVALID_INNER_BATCH,
temBAD_MPT,
temBAD_CIPHERTEXT,
};
//------------------------------------------------------------------------------
@@ -208,6 +210,7 @@ enum TERcodes : TERUnderlyingType {
terADDRESS_COLLISION, // Failed to allocate AccountID when trying to
// create a pseudo-account
terNO_DELEGATE_PERMISSION, // Delegate does not have permission
terLOCKED, // MPT is locked
};
//------------------------------------------------------------------------------
@@ -346,6 +349,7 @@ enum TECcodes : TERUnderlyingType {
// backward compatibility with historical data on non-prod networks, can be
// reclaimed after those networks reset.
tecNO_DELEGATE_PERMISSION = 198,
tecBAD_PROOF = 199,
};
//------------------------------------------------------------------------------

View File

@@ -138,7 +138,8 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TF_FLAG(tfMPTCanEscrow, lsfMPTCanEscrow) \
TF_FLAG(tfMPTCanTrade, lsfMPTCanTrade) \
TF_FLAG(tfMPTCanTransfer, lsfMPTCanTransfer) \
TF_FLAG(tfMPTCanClawback, lsfMPTCanClawback), \
TF_FLAG(tfMPTCanClawback, lsfMPTCanClawback) \
TF_FLAG(tfMPTCanConfidentialAmount, lsfMPTCanConfidentialAmount), \
MASK_ADJ(0)) \
\
TRANSACTION(MPTokenAuthorize, \
@@ -347,10 +348,12 @@ inline constexpr FlagValue tmfMPTCanMutateCanTransfer = lsmfMPTCanMutateCanTrans
inline constexpr FlagValue tmfMPTCanMutateCanClawback = lsmfMPTCanMutateCanClawback;
inline constexpr FlagValue tmfMPTCanMutateMetadata = lsmfMPTCanMutateMetadata;
inline constexpr FlagValue tmfMPTCanMutateTransferFee = lsmfMPTCanMutateTransferFee;
inline constexpr FlagValue tmfMPTokenIssuanceCreateMutableMask =
~(tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow |
tmfMPTCanMutateCanTrade | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback |
tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee);
inline constexpr FlagValue tmfMPTCannotMutateCanConfidentialAmount =
lsmfMPTCannotMutateCanConfidentialAmount;
inline constexpr FlagValue tmfMPTokenIssuanceCreateMutableMask = ~(
tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow |
tmfMPTCanMutateCanTrade | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback |
tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee | tmfMPTCannotMutateCanConfidentialAmount);
// MPTokenIssuanceSet MutableFlags:
// Set or Clear flags.
@@ -367,10 +370,13 @@ inline constexpr FlagValue tmfMPTSetCanTransfer = 0x00000100;
inline constexpr FlagValue tmfMPTClearCanTransfer = 0x00000200;
inline constexpr FlagValue tmfMPTSetCanClawback = 0x00000400;
inline constexpr FlagValue tmfMPTClearCanClawback = 0x00000800;
inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask = ~(
tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTSetRequireAuth | tmfMPTClearRequireAuth |
tmfMPTSetCanEscrow | tmfMPTClearCanEscrow | tmfMPTSetCanTrade | tmfMPTClearCanTrade |
tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | tmfMPTSetCanClawback | tmfMPTClearCanClawback);
inline constexpr FlagValue tmfMPTSetCanConfidentialAmount = 0x00001000;
inline constexpr FlagValue tmfMPTClearCanConfidentialAmount = 0x00002000;
inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask =
~(tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTSetRequireAuth | tmfMPTClearRequireAuth |
tmfMPTSetCanEscrow | tmfMPTClearCanEscrow | tmfMPTSetCanTrade | tmfMPTClearCanTrade |
tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | tmfMPTSetCanClawback |
tmfMPTClearCanClawback | tmfMPTSetCanConfidentialAmount | tmfMPTClearCanConfidentialAmount);
// Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between accounts allowed a
// TrustLine to be added to the issuer of that token without explicit permission from that issuer.

View File

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

View File

@@ -161,8 +161,10 @@ LEDGER_ENTRY(ltDIR_NODE, 0x0064, DirectoryNode, directory, ({
{sfOwner, soeOPTIONAL}, // for owner directories
{sfTakerPaysCurrency, soeOPTIONAL}, // order book directories
{sfTakerPaysIssuer, soeOPTIONAL}, // order book directories
{sfTakerPaysMPT, soeOPTIONAL}, // order book directories
{sfTakerGetsCurrency, soeOPTIONAL}, // order book directories
{sfTakerGetsIssuer, soeOPTIONAL}, // order book directories
{sfTakerGetsMPT, soeOPTIONAL}, // order book directories
{sfExchangeRate, soeOPTIONAL}, // order book directories
{sfIndexes, soeREQUIRED},
{sfRootIndex, soeREQUIRED},
@@ -385,32 +387,41 @@ LEDGER_ENTRY(ltAMM, 0x0079, AMM, amm, ({
\sa keylet::mptIssuance
*/
LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
{sfIssuer, soeREQUIRED},
{sfSequence, soeREQUIRED},
{sfTransferFee, soeDEFAULT},
{sfOwnerNode, soeREQUIRED},
{sfAssetScale, soeDEFAULT},
{sfMaximumAmount, soeOPTIONAL},
{sfOutstandingAmount, soeREQUIRED},
{sfLockedAmount, soeOPTIONAL},
{sfMPTokenMetadata, soeOPTIONAL},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
{sfDomainID, soeOPTIONAL},
{sfMutableFlags, soeDEFAULT},
{sfIssuer, soeREQUIRED},
{sfSequence, soeREQUIRED},
{sfTransferFee, soeDEFAULT},
{sfOwnerNode, soeREQUIRED},
{sfAssetScale, soeDEFAULT},
{sfMaximumAmount, soeOPTIONAL},
{sfOutstandingAmount, soeREQUIRED},
{sfLockedAmount, soeOPTIONAL},
{sfMPTokenMetadata, soeOPTIONAL},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
{sfDomainID, soeOPTIONAL},
{sfMutableFlags, soeDEFAULT},
{sfIssuerEncryptionKey, soeOPTIONAL},
{sfAuditorEncryptionKey, soeOPTIONAL},
{sfConfidentialOutstandingAmount, soeDEFAULT},
}))
/** A ledger object which tracks MPToken
\sa keylet::mptoken
*/
LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
{sfAccount, soeREQUIRED},
{sfMPTokenIssuanceID, soeREQUIRED},
{sfMPTAmount, soeDEFAULT},
{sfLockedAmount, soeOPTIONAL},
{sfOwnerNode, soeREQUIRED},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
{sfAccount, soeREQUIRED},
{sfMPTokenIssuanceID, soeREQUIRED},
{sfMPTAmount, soeDEFAULT},
{sfLockedAmount, soeOPTIONAL},
{sfOwnerNode, soeREQUIRED},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
{sfConfidentialBalanceInbox, soeOPTIONAL},
{sfConfidentialBalanceSpending, soeOPTIONAL},
{sfConfidentialBalanceVersion, soeDEFAULT},
{sfIssuerEncryptedBalance, soeOPTIONAL},
{sfAuditorEncryptedBalance, soeOPTIONAL},
{sfHolderEncryptionKey, soeOPTIONAL},
}))
/** A ledger object which tracks Oracle

View File

@@ -11,7 +11,10 @@ secp256k1Context()
struct holder
{
secp256k1_context* impl;
holder() : impl(secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))
// SECP256K1_CONTEXT_SIGN and SECP256K1_CONTEXT_VERIFY were
// deprecated. All contexts support both signing and verification, so
// SECP256K1_CONTEXT_NONE is the correct flag to use.
holder() : impl(secp256k1_context_create(SECP256K1_CONTEXT_NONE))
{
}

View File

@@ -113,6 +113,7 @@ TYPED_SFIELD(sfInterestRate, UINT32, 65) // 1/10 basis points (bi
TYPED_SFIELD(sfLateInterestRate, UINT32, 66) // 1/10 basis points (bips)
TYPED_SFIELD(sfCloseInterestRate, UINT32, 67) // 1/10 basis points (bips)
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bips)
TYPED_SFIELD(sfConfidentialBalanceVersion, UINT32, 69)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -146,6 +147,7 @@ TYPED_SFIELD(sfSubjectNode, UINT64, 28)
TYPED_SFIELD(sfLockedAmount, UINT64, 29, SField::sMD_BaseTen|SField::sMD_Default)
TYPED_SFIELD(sfVaultNode, UINT64, 30)
TYPED_SFIELD(sfLoanBrokerNode, UINT64, 31)
TYPED_SFIELD(sfConfidentialOutstandingAmount, UINT64, 32, SField::sMD_BaseTen|SField::sMD_Default)
// 128-bit
TYPED_SFIELD(sfEmailHash, UINT128, 1)
@@ -159,6 +161,8 @@ TYPED_SFIELD(sfTakerGetsIssuer, UINT160, 4)
// 192-bit (common)
TYPED_SFIELD(sfMPTokenIssuanceID, UINT192, 1)
TYPED_SFIELD(sfShareMPTID, UINT192, 2)
TYPED_SFIELD(sfTakerPaysMPT, UINT192, 3)
TYPED_SFIELD(sfTakerGetsMPT, UINT192, 4)
// 256-bit (common)
TYPED_SFIELD(sfLedgerHash, UINT256, 1)
@@ -203,6 +207,7 @@ TYPED_SFIELD(sfParentBatchID, UINT256, 36)
TYPED_SFIELD(sfLoanBrokerID, UINT256, 37,
SField::sMD_PseudoAccount | SField::sMD_Default)
TYPED_SFIELD(sfLoanID, UINT256, 38)
TYPED_SFIELD(sfBlindingFactor, UINT256, 39)
// number (common)
TYPED_SFIELD(sfNumber, NUMBER, 1)
@@ -296,6 +301,21 @@ TYPED_SFIELD(sfAssetClass, VL, 28)
TYPED_SFIELD(sfProvider, VL, 29)
TYPED_SFIELD(sfMPTokenMetadata, VL, 30)
TYPED_SFIELD(sfCredentialType, VL, 31)
TYPED_SFIELD(sfConfidentialBalanceInbox, VL, 32)
TYPED_SFIELD(sfConfidentialBalanceSpending, VL, 33)
TYPED_SFIELD(sfIssuerEncryptedBalance, VL, 34)
TYPED_SFIELD(sfIssuerEncryptionKey, VL, 35)
TYPED_SFIELD(sfHolderEncryptionKey, VL, 36)
TYPED_SFIELD(sfZKProof, VL, 37)
TYPED_SFIELD(sfHolderEncryptedAmount, VL, 38)
TYPED_SFIELD(sfIssuerEncryptedAmount, VL, 39)
TYPED_SFIELD(sfSenderEncryptedAmount, VL, 40)
TYPED_SFIELD(sfDestinationEncryptedAmount, VL, 41)
TYPED_SFIELD(sfAuditorEncryptedBalance, VL, 42)
TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43)
TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44)
TYPED_SFIELD(sfAmountCommitment, VL, 45)
TYPED_SFIELD(sfBalanceCommitment, VL, 46)
// account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1)

View File

@@ -27,7 +27,7 @@
TRANSACTION(ttPAYMENT, 0, Payment,
Delegation::delegable,
uint256{},
createAcct,
createAcct | mayCreateMPT,
({
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported},
@@ -129,10 +129,10 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey,
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate,
Delegation::delegable,
uint256{},
noPriv,
mayCreateMPT,
({
{sfTakerPays, soeREQUIRED},
{sfTakerGets, soeREQUIRED},
{sfTakerPays, soeREQUIRED, soeMPTSupported},
{sfTakerGets, soeREQUIRED, soeMPTSupported},
{sfExpiration, soeOPTIONAL},
{sfOfferSequence, soeOPTIONAL},
{sfDomainID, soeOPTIONAL},
@@ -239,7 +239,7 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
noPriv,
({
{sfDestination, soeREQUIRED},
{sfSendMax, soeREQUIRED},
{sfSendMax, soeREQUIRED, soeMPTSupported},
{sfExpiration, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfInvoiceID, soeOPTIONAL},
@@ -252,11 +252,11 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
TRANSACTION(ttCHECK_CASH, 17, CheckCash,
Delegation::delegable,
uint256{},
noPriv,
mayCreateMPT,
({
{sfCheckID, soeREQUIRED},
{sfAmount, soeOPTIONAL},
{sfDeliverMin, soeOPTIONAL},
{sfAmount, soeOPTIONAL, soeMPTSupported},
{sfDeliverMin, soeOPTIONAL, soeMPTSupported},
}))
/** This transaction type cancels an existing check. */
@@ -409,12 +409,12 @@ TRANSACTION(ttCLAWBACK, 30, Clawback,
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
Delegation::delegable,
featureAMMClawback,
mayDeleteAcct | overrideFreeze,
mayDeleteAcct | overrideFreeze | mayAuthorizeMPT,
({
{sfHolder, soeREQUIRED},
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
{sfAmount, soeOPTIONAL},
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAsset2, soeREQUIRED, soeMPTSupported},
{sfAmount, soeOPTIONAL, soeMPTSupported},
}))
/** This transaction type creates an AMM instance */
@@ -424,10 +424,10 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
TRANSACTION(ttAMM_CREATE, 35, AMMCreate,
Delegation::delegable,
featureAMM,
createPseudoAcct,
createPseudoAcct | mayCreateMPT,
({
{sfAmount, soeREQUIRED},
{sfAmount2, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported},
{sfAmount2, soeREQUIRED, soeMPTSupported},
{sfTradingFee, soeREQUIRED},
}))
@@ -440,10 +440,10 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
featureAMM,
noPriv,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
{sfAmount, soeOPTIONAL},
{sfAmount2, soeOPTIONAL},
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAsset2, soeREQUIRED, soeMPTSupported},
{sfAmount, soeOPTIONAL, soeMPTSupported},
{sfAmount2, soeOPTIONAL, soeMPTSupported},
{sfEPrice, soeOPTIONAL},
{sfLPTokenOut, soeOPTIONAL},
{sfTradingFee, soeOPTIONAL},
@@ -456,12 +456,12 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw,
Delegation::delegable,
featureAMM,
mayDeleteAcct,
mayDeleteAcct | mayAuthorizeMPT,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
{sfAmount, soeOPTIONAL},
{sfAmount2, soeOPTIONAL},
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAsset2, soeREQUIRED, soeMPTSupported},
{sfAmount, soeOPTIONAL, soeMPTSupported},
{sfAmount2, soeOPTIONAL, soeMPTSupported},
{sfEPrice, soeOPTIONAL},
{sfLPTokenIn, soeOPTIONAL},
}))
@@ -475,8 +475,8 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote,
featureAMM,
noPriv,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAsset2, soeREQUIRED, soeMPTSupported},
{sfTradingFee, soeREQUIRED},
}))
@@ -489,8 +489,8 @@ TRANSACTION(ttAMM_BID, 39, AMMBid,
featureAMM,
noPriv,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAsset2, soeREQUIRED, soeMPTSupported},
{sfBidMin, soeOPTIONAL},
{sfBidMax, soeOPTIONAL},
{sfAuthAccounts, soeOPTIONAL},
@@ -503,10 +503,10 @@ TRANSACTION(ttAMM_BID, 39, AMMBid,
TRANSACTION(ttAMM_DELETE, 40, AMMDelete,
Delegation::delegable,
featureAMM,
mustDeleteAcct,
mustDeleteAcct | mayDeleteMPT,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAsset2, soeREQUIRED, soeMPTSupported},
}))
/** This transactions creates a crosschain sequence number */
@@ -734,6 +734,8 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
{sfMPTokenMetadata, soeOPTIONAL},
{sfTransferFee, soeOPTIONAL},
{sfMutableFlags, soeOPTIONAL},
{sfIssuerEncryptionKey, soeOPTIONAL},
{sfAuditorEncryptionKey, soeOPTIONAL},
}))
/** This transaction type authorizes a MPToken instance */
@@ -1076,6 +1078,90 @@ TRANSACTION(ttLOAN_PAY, 84, LoanPay,
{sfAmount, soeREQUIRED, soeMPTSupported},
}))
/** This transaction type converts into confidential MPT balance. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/token/ConfidentialMPTConvert.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_CONVERT, 85, ConfidentialMPTConvert,
Delegation::delegable,
featureConfidentialTransfer,
noPriv,
({
{sfMPTokenIssuanceID, soeREQUIRED},
{sfMPTAmount, soeREQUIRED},
{sfHolderEncryptionKey, soeOPTIONAL},
{sfHolderEncryptedAmount, soeREQUIRED},
{sfIssuerEncryptedAmount, soeREQUIRED},
{sfAuditorEncryptedAmount, soeOPTIONAL},
{sfBlindingFactor, soeREQUIRED},
{sfZKProof, soeOPTIONAL},
}))
/** This transaction type merges MPT inbox. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/token/ConfidentialMPTMergeInbox.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_MERGE_INBOX, 86, ConfidentialMPTMergeInbox,
Delegation::delegable,
featureConfidentialTransfer,
noPriv,
({
{sfMPTokenIssuanceID, soeREQUIRED},
}))
/** This transaction type converts back into public MPT balance. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/token/ConfidentialMPTConvertBack.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_CONVERT_BACK, 87, ConfidentialMPTConvertBack,
Delegation::delegable,
featureConfidentialTransfer,
noPriv,
({
{sfMPTokenIssuanceID, soeREQUIRED},
{sfMPTAmount, soeREQUIRED},
{sfHolderEncryptedAmount, soeREQUIRED},
{sfIssuerEncryptedAmount, soeREQUIRED},
{sfAuditorEncryptedAmount, soeOPTIONAL},
{sfBlindingFactor, soeREQUIRED},
{sfZKProof, soeREQUIRED},
{sfBalanceCommitment, soeREQUIRED},
}))
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/token/ConfidentialMPTSend.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_SEND, 88, ConfidentialMPTSend,
Delegation::delegable,
featureConfidentialTransfer,
noPriv,
({
{sfMPTokenIssuanceID, soeREQUIRED},
{sfDestination, soeREQUIRED},
{sfSenderEncryptedAmount, soeREQUIRED},
{sfDestinationEncryptedAmount, soeREQUIRED},
{sfIssuerEncryptedAmount, soeREQUIRED},
{sfAuditorEncryptedAmount, soeOPTIONAL},
{sfZKProof, soeREQUIRED},
{sfAmountCommitment, soeREQUIRED},
{sfBalanceCommitment, soeREQUIRED},
{sfCredentialIDs, soeOPTIONAL},
}))
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/token/ConfidentialMPTClawback.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_CLAWBACK, 89, ConfidentialMPTClawback,
Delegation::delegable,
featureConfidentialTransfer,
noPriv,
({
{sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeREQUIRED},
{sfMPTAmount, soeREQUIRED},
{sfZKProof, soeREQUIRED},
}))
/** This system-generated transaction type is used to update the status of the various amendments.
For details, see: https://xrpl.org/amendments.html

View File

@@ -137,6 +137,7 @@ JSS(authorized_credentials); // in: ledger_entry DepositPreauth
JSS(auth_accounts); // out: amm_info
JSS(auth_change); // out: AccountInfo
JSS(auth_change_queued); // out: AccountInfo
JSS(auditor_encrypted_balance); // out: mpt_holders (confidential MPT)
JSS(available); // out: ValidatorList
JSS(avg_bps_recv); // out: Peers
JSS(avg_bps_sent); // out: Peers
@@ -161,9 +162,6 @@ JSS(build_path); // in: TransactionSign
JSS(build_version); // out: NetworkOPs
JSS(cancel_after); // out: AccountChannels
JSS(can_delete); // out: CanDelete
JSS(mpt_amount); // out: mpt_holders
JSS(mpt_issuance_id); // in: Payment, mpt_holders
JSS(mptoken_index); // out: mpt_holders
JSS(changes); // out: BookChanges
JSS(channel_id); // out: AccountChannels
JSS(channels); // out: AccountChannels
@@ -185,165 +183,170 @@ JSS(command); // in: RPCHandler
JSS(common); // out: RPC server_definitions
JSS(complete); // out: NetworkOPs, InboundLedger
JSS(complete_ledgers); // out: NetworkOPs, PeerImp
JSS(consensus); // out: NetworkOPs, LedgerConsensus
JSS(converge_time); // out: NetworkOPs
JSS(converge_time_s); // out: NetworkOPs
JSS(cookie); // out: NetworkOPs
JSS(count); // in: AccountTx*, ValidatorList
JSS(counters); // in/out: retrieve counters
JSS(credentials); // in: deposit_authorized
JSS(credential_type); // in: LedgerEntry DepositPreauth
JSS(ctid); // in/out: Tx RPC
JSS(currency_a); // out: BookChanges
JSS(currency_b); // out: BookChanges
JSS(currency); // in: paths/PathRequest, STAmount
// out: STPathSet, STAmount, AccountLines
JSS(current); // out: OwnerInfo
JSS(current_activities); //
JSS(current_ledger_size); // out: TxQ
JSS(current_queue_size); // out: TxQ
JSS(data); // out: LedgerData
JSS(date); // out: tx/Transaction, NetworkOPs
JSS(dbKBLedger); // out: getCounts
JSS(dbKBTotal); // out: getCounts
JSS(dbKBTransaction); // out: getCounts
JSS(debug_signing); // in: TransactionSign
JSS(deletion_blockers_only); // in: AccountObjects
JSS(delivered_amount); // out: insertDeliveredAmount
JSS(deposit_authorized); // out: deposit_authorized
JSS(deprecated); //
JSS(descending); // in: AccountTx*
JSS(description); // in/out: Reservations
JSS(destination); // in: nft_buy_offers, nft_sell_offers
JSS(destination_account); // in: PathRequest, RipplePathFind, account_lines
// out: AccountChannels
JSS(destination_amount); // in: PathRequest, RipplePathFind
JSS(destination_currencies); // in: PathRequest, RipplePathFind
JSS(destination_tag); // in: PathRequest
// out: AccountChannels
JSS(details); // out: Manifest, server_info
JSS(dir_entry); // out: DirectoryEntryIterator
JSS(dir_index); // out: DirectoryEntryIterator
JSS(dir_root); // out: DirectoryEntryIterator
JSS(discounted_fee); // out: amm_info
JSS(domain); // out: ValidatorInfo, Manifest
JSS(drops); // out: TxQ
JSS(duration_us); // out: NetworkOPs
JSS(effective); // out: ValidatorList
// in: UNL
JSS(enabled); // out: AmendmentTable
JSS(engine_result); // out: NetworkOPs, TransactionSign, Submit
JSS(engine_result_code); // out: NetworkOPs, TransactionSign, Submit
JSS(engine_result_message); // out: NetworkOPs, TransactionSign, Submit
JSS(entire_set); // out: get_aggregate_price
JSS(ephemeral_key); // out: ValidatorInfo
// in/out: Manifest
JSS(error); // out: error
JSS(errored); //
JSS(error_code); // out: error
JSS(error_exception); // out: Submit
JSS(error_message); // out: error
JSS(expand); // in: handler/Ledger
JSS(expected_date); // out: any (warnings)
JSS(expected_date_UTC); // out: any (warnings)
JSS(expected_ledger_size); // out: TxQ
JSS(expiration); // out: AccountOffers, AccountChannels, ValidatorList, amm_info
JSS(fail_hard); // in: Sign, Submit
JSS(failed); // out: InboundLedger
JSS(feature); // in: Feature
JSS(features); // out: Feature
JSS(fee_base); // out: NetworkOPs
JSS(fee_div_max); // in: TransactionSign
JSS(fee_level); // out: AccountInfo
JSS(fee_mult_max); // in: TransactionSign
JSS(fee_ref); // out: NetworkOPs, DEPRECATED
JSS(fetch_pack); // out: NetworkOPs
JSS(FIELDS); // out: RPC server_definitions
// matches definitions.json format
JSS(first); // out: rpc/Version
JSS(finished); //
JSS(fix_txns); // in: LedgerCleaner
JSS(flags); // out: AccountOffers, NetworkOPs
JSS(forward); // in: AccountTx
JSS(freeze); // out: AccountLines
JSS(freeze_peer); // out: AccountLines
JSS(deep_freeze); // out: AccountLines
JSS(deep_freeze_peer); // out: AccountLines
JSS(frozen_balances); // out: GatewayBalances
JSS(full); // in: LedgerClearer, handlers/Ledger
JSS(full_reply); // out: PathFind
JSS(fullbelow_size); // out: GetCounts
JSS(git); // out: server_info
JSS(good); // out: RPCVersion
JSS(hash); // out: NetworkOPs, InboundLedger, LedgerToJson, STTx; field
JSS(have_header); // out: InboundLedger
JSS(have_state); // out: InboundLedger
JSS(have_transactions); // out: InboundLedger
JSS(high); // out: BookChanges
JSS(highest_sequence); // out: AccountInfo
JSS(highest_ticket); // out: AccountInfo
JSS(historical_perminute); // historical_perminute.
JSS(holders); // out: MPTHolders
JSS(hostid); // out: NetworkOPs
JSS(hotwallet); // in: GatewayBalances
JSS(id); // websocket.
JSS(ident); // in: AccountCurrencies, AccountInfo, OwnerInfo
JSS(ignore_default); // in: AccountLines
JSS(in); // out: OverlayImpl
JSS(inLedger); // out: tx/Transaction
JSS(inbound); // out: PeerImp
JSS(index); // in: LedgerEntry
// out: STLedgerEntry, LedgerEntry, TxHistory, LedgerData
JSS(info); // out: ServerInfo, ConsensusInfo, FetchInfo
JSS(initial_sync_duration_us); //
JSS(internal_command); // in: Internal
JSS(invalid_API_version); // out: Many, when a request has an invalid version
JSS(io_latency_ms); // out: NetworkOPs
JSS(ip); // in: Connect, out: OverlayImpl
JSS(is_burned); // out: nft_info (clio)
JSS(isSerialized); // out: RPC server_definitions
// matches definitions.json format
JSS(isSigningField); // out: RPC server_definitions
// matches definitions.json format
JSS(isVLEncoded); // out: RPC server_definitions
// matches definitions.json format
JSS(issuer); // in: RipplePathFind, Subscribe, Unsubscribe, BookOffers
// out: STPathSet, STAmount
JSS(job); //
JSS(job_queue); //
JSS(jobs); //
JSS(jsonrpc); // json version
JSS(jq_trans_overflow); // JobQueue transaction limit overflow.
JSS(kept); // out: SubmitTransaction
JSS(key); // out
JSS(key_type); // in/out: WalletPropose, TransactionSign
JSS(latency); // out: PeerImp
JSS(last); // out: RPCVersion
JSS(last_close); // out: NetworkOPs
JSS(last_refresh_time); // out: ValidatorSite
JSS(last_refresh_status); // out: ValidatorSite
JSS(last_refresh_message); // out: ValidatorSite
JSS(ledger); // in: NetworkOPs, LedgerCleaner, RPCHelpers
// out: NetworkOPs, PeerImp
JSS(ledger_current_index); // out: NetworkOPs, RPCHelpers, LedgerCurrent, LedgerAccept,
// AccountLines
JSS(ledger_data); // out: LedgerHeader
JSS(ledger_hash); // in: RPCHelpers, LedgerRequest, RipplePathFind,
// TransactionEntry, handlers/Ledger
// out: NetworkOPs, RPCHelpers, LedgerClosed, LedgerData,
// AccountLines
JSS(ledger_hit_rate); // out: GetCounts
JSS(ledger_index); // in/out: many
JSS(ledger_index_max); // in, out: AccountTx*
JSS(ledger_index_min); // in, out: AccountTx*
JSS(ledger_max); // in, out: AccountTx*
JSS(ledger_min); // in, out: AccountTx*
JSS(ledger_time); // out: NetworkOPs
JSS(LEDGER_ENTRY_TYPES); // out: RPC server_definitions
// matches definitions.json format
JSS(LEDGER_ENTRY_FLAGS); // out: RPC server_definitions
JSS(LEDGER_ENTRY_FORMATS); // out: RPC server_definitions
JSS(levels); // LogLevels
JSS(confidential_balance_inbox); // out: mpt_holders (confidential MPT)
JSS(confidential_balance_spending); // out: mpt_holders (confidential MPT)
JSS(confidential_balance_version); // out: mpt_holders (confidential MPT)
JSS(consensus); // out: NetworkOPs, LedgerConsensus
JSS(converge_time); // out: NetworkOPs
JSS(converge_time_s); // out: NetworkOPs
JSS(cookie); // out: NetworkOPs
JSS(count); // in: AccountTx*, ValidatorList
JSS(counters); // in/out: retrieve counters
JSS(credentials); // in: deposit_authorized
JSS(credential_type); // in: LedgerEntry DepositPreauth
JSS(ctid); // in/out: Tx RPC
JSS(currency_a); // out: BookChanges
JSS(currency_b); // out: BookChanges
JSS(currency); // in: paths/PathRequest, STAmount
// out: STPathSet, STAmount, AccountLines
JSS(current); // out: OwnerInfo
JSS(current_activities); //
JSS(current_ledger_size); // out: TxQ
JSS(current_queue_size); // out: TxQ
JSS(data); // out: LedgerData
JSS(date); // out: tx/Transaction, NetworkOPs
JSS(dbKBLedger); // out: getCounts
JSS(dbKBTotal); // out: getCounts
JSS(dbKBTransaction); // out: getCounts
JSS(debug_signing); // in: TransactionSign
JSS(deletion_blockers_only); // in: AccountObjects
JSS(delivered_amount); // out: insertDeliveredAmount
JSS(deposit_authorized); // out: deposit_authorized
JSS(deprecated); //
JSS(descending); // in: AccountTx*
JSS(description); // in/out: Reservations
JSS(destination); // in: nft_buy_offers, nft_sell_offers
JSS(destination_account); // in: PathRequest, RipplePathFind, account_lines
// out: AccountChannels
JSS(destination_amount); // in: PathRequest, RipplePathFind
JSS(destination_currencies); // in: PathRequest, RipplePathFind
JSS(destination_tag); // in: PathRequest
// out: AccountChannels
JSS(details); // out: Manifest, server_info
JSS(dir_entry); // out: DirectoryEntryIterator
JSS(dir_index); // out: DirectoryEntryIterator
JSS(dir_root); // out: DirectoryEntryIterator
JSS(discounted_fee); // out: amm_info
JSS(domain); // out: ValidatorInfo, Manifest
JSS(drops); // out: TxQ
JSS(duration_us); // out: NetworkOPs
JSS(effective); // out: ValidatorList
// in: UNL
JSS(enabled); // out: AmendmentTable
JSS(engine_result); // out: NetworkOPs, TransactionSign, Submit
JSS(engine_result_code); // out: NetworkOPs, TransactionSign, Submit
JSS(engine_result_message); // out: NetworkOPs, TransactionSign, Submit
JSS(entire_set); // out: get_aggregate_price
JSS(ephemeral_key); // out: ValidatorInfo
// in/out: Manifest
JSS(error); // out: error
JSS(errored); //
JSS(error_code); // out: error
JSS(error_exception); // out: Submit
JSS(error_message); // out: error
JSS(expand); // in: handler/Ledger
JSS(expected_date); // out: any (warnings)
JSS(expected_date_UTC); // out: any (warnings)
JSS(expected_ledger_size); // out: TxQ
JSS(expiration); // out: AccountOffers, AccountChannels, ValidatorList, amm_info
JSS(fail_hard); // in: Sign, Submit
JSS(failed); // out: InboundLedger
JSS(feature); // in: Feature
JSS(features); // out: Feature
JSS(fee_base); // out: NetworkOPs
JSS(fee_div_max); // in: TransactionSign
JSS(fee_level); // out: AccountInfo
JSS(fee_mult_max); // in: TransactionSign
JSS(fee_ref); // out: NetworkOPs, DEPRECATED
JSS(fetch_pack); // out: NetworkOPs
JSS(FIELDS); // out: RPC server_definitions
// matches definitions.json format
JSS(first); // out: rpc/Version
JSS(finished); //
JSS(fix_txns); // in: LedgerCleaner
JSS(flags); // out: AccountOffers, NetworkOPs
JSS(forward); // in: AccountTx
JSS(freeze); // out: AccountLines
JSS(freeze_peer); // out: AccountLines
JSS(deep_freeze); // out: AccountLines
JSS(deep_freeze_peer); // out: AccountLines
JSS(frozen_balances); // out: GatewayBalances
JSS(full); // in: LedgerClearer, handlers/Ledger
JSS(full_reply); // out: PathFind
JSS(fullbelow_size); // out: GetCounts
JSS(git); // out: server_info
JSS(good); // out: RPCVersion
JSS(hash); // out: NetworkOPs, InboundLedger, LedgerToJson, STTx; field
JSS(have_header); // out: InboundLedger
JSS(have_state); // out: InboundLedger
JSS(have_transactions); // out: InboundLedger
JSS(high); // out: BookChanges
JSS(highest_sequence); // out: AccountInfo
JSS(highest_ticket); // out: AccountInfo
JSS(historical_perminute); // historical_perminute.
JSS(holders); // out: MPTHolders
JSS(holder_encryption_key); // out: mpt_holders (confidential MPT)
JSS(hostid); // out: NetworkOPs
JSS(hotwallet); // in: GatewayBalances
JSS(id); // websocket.
JSS(ident); // in: AccountCurrencies, AccountInfo, OwnerInfo
JSS(ignore_default); // in: AccountLines
JSS(in); // out: OverlayImpl
JSS(inLedger); // out: tx/Transaction
JSS(inbound); // out: PeerImp
JSS(index); // in: LedgerEntry
// out: STLedgerEntry, LedgerEntry, TxHistory, LedgerData
JSS(info); // out: ServerInfo, ConsensusInfo, FetchInfo
JSS(initial_sync_duration_us); //
JSS(internal_command); // in: Internal
JSS(invalid_API_version); // out: Many, when a request has an invalid version
JSS(io_latency_ms); // out: NetworkOPs
JSS(ip); // in: Connect, out: OverlayImpl
JSS(is_burned); // out: nft_info (clio)
JSS(isSerialized); // out: RPC server_definitions
// matches definitions.json format
JSS(isSigningField); // out: RPC server_definitions
// matches definitions.json format
JSS(isVLEncoded); // out: RPC server_definitions
// matches definitions.json format
JSS(issuer); // in: RipplePathFind, Subscribe, Unsubscribe, BookOffers
// out: STPathSet, STAmount
JSS(issuer_encrypted_balance); // out: mpt_holders (confidential MPT)
JSS(job); //
JSS(job_queue); //
JSS(jobs); //
JSS(jsonrpc); // json version
JSS(jq_trans_overflow); // JobQueue transaction limit overflow.
JSS(kept); // out: SubmitTransaction
JSS(key); // out
JSS(key_type); // in/out: WalletPropose, TransactionSign
JSS(latency); // out: PeerImp
JSS(last); // out: RPCVersion
JSS(last_close); // out: NetworkOPs
JSS(last_refresh_time); // out: ValidatorSite
JSS(last_refresh_status); // out: ValidatorSite
JSS(last_refresh_message); // out: ValidatorSite
JSS(ledger); // in: NetworkOPs, LedgerCleaner, RPCHelpers
// out: NetworkOPs, PeerImp
JSS(ledger_current_index); // out: NetworkOPs, RPCHelpers, LedgerCurrent, LedgerAccept,
// AccountLines
JSS(ledger_data); // out: LedgerHeader
JSS(ledger_hash); // in: RPCHelpers, LedgerRequest, RipplePathFind,
// TransactionEntry, handlers/Ledger
// out: NetworkOPs, RPCHelpers, LedgerClosed, LedgerData,
// AccountLines
JSS(ledger_hit_rate); // out: GetCounts
JSS(ledger_index); // in/out: many
JSS(ledger_index_max); // in, out: AccountTx*
JSS(ledger_index_min); // in, out: AccountTx*
JSS(ledger_max); // in, out: AccountTx*
JSS(ledger_min); // in, out: AccountTx*
JSS(ledger_time); // out: NetworkOPs
JSS(LEDGER_ENTRY_TYPES); // out: RPC server_definitions
// matches definitions.json format
JSS(LEDGER_ENTRY_FLAGS); // out: RPC server_definitions
JSS(LEDGER_ENTRY_FORMATS); // out: RPC server_definitions
JSS(levels); // LogLevels
JSS(limit); // in/out: AccountTx*, AccountOffers, AccountLines, AccountObjects
// in: LedgerData, BookOffers
JSS(limit_peer); // out: AccountLines
@@ -401,6 +404,11 @@ JSS(min_ledger); // in: LedgerCleaner
JSS(minimum_fee); // out: TxQ
JSS(minimum_level); // out: TxQ
JSS(missingCommand); // error
JSS(mpt_amount); // out: mpt_holders
JSS(mpt_issuance_id); // in: Payment, mpt_holders
JSS(mptoken_index); // out: mpt_holders
JSS(mpt_issuance_id_a); // out: BookChanges
JSS(mpt_issuance_id_b); // out: BookChanges
JSS(name); // out: AmendmentTableImpl, PeerImp
JSS(needed_state_hashes); // out: InboundLedger
JSS(needed_transaction_hashes); // out: InboundLedger

View File

@@ -117,6 +117,30 @@ public:
return this->sle_->isFieldPresent(sfTakerPaysIssuer);
}
/**
* @brief Get sfTakerPaysMPT (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT192::type::value_type>
getTakerPaysMPT() const
{
if (hasTakerPaysMPT())
return this->sle_->at(sfTakerPaysMPT);
return std::nullopt;
}
/**
* @brief Check if sfTakerPaysMPT is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasTakerPaysMPT() const
{
return this->sle_->isFieldPresent(sfTakerPaysMPT);
}
/**
* @brief Get sfTakerGetsCurrency (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
@@ -165,6 +189,30 @@ public:
return this->sle_->isFieldPresent(sfTakerGetsIssuer);
}
/**
* @brief Get sfTakerGetsMPT (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT192::type::value_type>
getTakerGetsMPT() const
{
if (hasTakerGetsMPT())
return this->sle_->at(sfTakerGetsMPT);
return std::nullopt;
}
/**
* @brief Check if sfTakerGetsMPT is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasTakerGetsMPT() const
{
return this->sle_->isFieldPresent(sfTakerGetsMPT);
}
/**
* @brief Get sfExchangeRate (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
@@ -427,6 +475,17 @@ public:
return *this;
}
/**
* @brief Set sfTakerPaysMPT (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
DirectoryNodeBuilder&
setTakerPaysMPT(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfTakerPaysMPT] = value;
return *this;
}
/**
* @brief Set sfTakerGetsCurrency (soeOPTIONAL)
* @return Reference to this builder for method chaining.
@@ -449,6 +508,17 @@ public:
return *this;
}
/**
* @brief Set sfTakerGetsMPT (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
DirectoryNodeBuilder&
setTakerGetsMPT(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfTakerGetsMPT] = value;
return *this;
}
/**
* @brief Set sfExchangeRate (soeOPTIONAL)
* @return Reference to this builder for method chaining.

View File

@@ -147,6 +147,150 @@ public:
{
return this->sle_->at(sfPreviousTxnLgrSeq);
}
/**
* @brief Get sfConfidentialBalanceInbox (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getConfidentialBalanceInbox() const
{
if (hasConfidentialBalanceInbox())
return this->sle_->at(sfConfidentialBalanceInbox);
return std::nullopt;
}
/**
* @brief Check if sfConfidentialBalanceInbox is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasConfidentialBalanceInbox() const
{
return this->sle_->isFieldPresent(sfConfidentialBalanceInbox);
}
/**
* @brief Get sfConfidentialBalanceSpending (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getConfidentialBalanceSpending() const
{
if (hasConfidentialBalanceSpending())
return this->sle_->at(sfConfidentialBalanceSpending);
return std::nullopt;
}
/**
* @brief Check if sfConfidentialBalanceSpending is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasConfidentialBalanceSpending() const
{
return this->sle_->isFieldPresent(sfConfidentialBalanceSpending);
}
/**
* @brief Get sfConfidentialBalanceVersion (soeDEFAULT)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getConfidentialBalanceVersion() const
{
if (hasConfidentialBalanceVersion())
return this->sle_->at(sfConfidentialBalanceVersion);
return std::nullopt;
}
/**
* @brief Check if sfConfidentialBalanceVersion is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasConfidentialBalanceVersion() const
{
return this->sle_->isFieldPresent(sfConfidentialBalanceVersion);
}
/**
* @brief Get sfIssuerEncryptedBalance (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getIssuerEncryptedBalance() const
{
if (hasIssuerEncryptedBalance())
return this->sle_->at(sfIssuerEncryptedBalance);
return std::nullopt;
}
/**
* @brief Check if sfIssuerEncryptedBalance is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerEncryptedBalance() const
{
return this->sle_->isFieldPresent(sfIssuerEncryptedBalance);
}
/**
* @brief Get sfAuditorEncryptedBalance (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptedBalance() const
{
if (hasAuditorEncryptedBalance())
return this->sle_->at(sfAuditorEncryptedBalance);
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptedBalance is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptedBalance() const
{
return this->sle_->isFieldPresent(sfAuditorEncryptedBalance);
}
/**
* @brief Get sfHolderEncryptionKey (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getHolderEncryptionKey() const
{
if (hasHolderEncryptionKey())
return this->sle_->at(sfHolderEncryptionKey);
return std::nullopt;
}
/**
* @brief Check if sfHolderEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasHolderEncryptionKey() const
{
return this->sle_->isFieldPresent(sfHolderEncryptionKey);
}
};
/**
@@ -270,6 +414,72 @@ public:
return *this;
}
/**
* @brief Set sfConfidentialBalanceInbox (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setConfidentialBalanceInbox(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfConfidentialBalanceInbox] = value;
return *this;
}
/**
* @brief Set sfConfidentialBalanceSpending (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setConfidentialBalanceSpending(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfConfidentialBalanceSpending] = value;
return *this;
}
/**
* @brief Set sfConfidentialBalanceVersion (soeDEFAULT)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setConfidentialBalanceVersion(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfConfidentialBalanceVersion] = value;
return *this;
}
/**
* @brief Set sfIssuerEncryptedBalance (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setIssuerEncryptedBalance(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptedBalance] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptedBalance (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setAuditorEncryptedBalance(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptedBalance] = value;
return *this;
}
/**
* @brief Set sfHolderEncryptionKey (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setHolderEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfHolderEncryptionKey] = value;
return *this;
}
/**
* @brief Build and return the completed MPToken wrapper.
* @param index The ledger entry index.

View File

@@ -278,6 +278,78 @@ public:
{
return this->sle_->isFieldPresent(sfMutableFlags);
}
/**
* @brief Get sfIssuerEncryptionKey (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getIssuerEncryptionKey() const
{
if (hasIssuerEncryptionKey())
return this->sle_->at(sfIssuerEncryptionKey);
return std::nullopt;
}
/**
* @brief Check if sfIssuerEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerEncryptionKey() const
{
return this->sle_->isFieldPresent(sfIssuerEncryptionKey);
}
/**
* @brief Get sfAuditorEncryptionKey (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptionKey() const
{
if (hasAuditorEncryptionKey())
return this->sle_->at(sfAuditorEncryptionKey);
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptionKey() const
{
return this->sle_->isFieldPresent(sfAuditorEncryptionKey);
}
/**
* @brief Get sfConfidentialOutstandingAmount (soeDEFAULT)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT64::type::value_type>
getConfidentialOutstandingAmount() const
{
if (hasConfidentialOutstandingAmount())
return this->sle_->at(sfConfidentialOutstandingAmount);
return std::nullopt;
}
/**
* @brief Check if sfConfidentialOutstandingAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasConfidentialOutstandingAmount() const
{
return this->sle_->isFieldPresent(sfConfidentialOutstandingAmount);
}
};
/**
@@ -469,6 +541,39 @@ public:
return *this;
}
/**
* @brief Set sfIssuerEncryptionKey (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceBuilder&
setIssuerEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptionKey] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptionKey (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceBuilder&
setAuditorEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptionKey] = value;
return *this;
}
/**
* @brief Set sfConfidentialOutstandingAmount (soeDEFAULT)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceBuilder&
setConfidentialOutstandingAmount(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfConfidentialOutstandingAmount] = value;
return *this;
}
/**
* @brief Build and return the completed MPTokenIssuance wrapper.
* @param index The ledger entry index.

View File

@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -192,6 +194,7 @@ public:
/**
* @brief Set sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMBidBuilder&
@@ -203,6 +206,7 @@ public:
/**
* @brief Set sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMBidBuilder&

View File

@@ -21,7 +21,7 @@ class AMMClawbackBuilder;
* Type: ttAMM_CLAWBACK (31)
* Delegable: Delegation::delegable
* Amendment: featureAMMClawback
* Privileges: mayDeleteAcct | overrideFreeze
* Privileges: mayDeleteAcct | overrideFreeze | mayAuthorizeMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use AMMClawbackBuilder to construct new transactions.
@@ -60,6 +60,7 @@ public:
/**
* @brief Get sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -71,6 +72,7 @@ public:
/**
* @brief Get sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -82,6 +84,7 @@ public:
/**
* @brief Get sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -166,6 +169,7 @@ public:
/**
* @brief Set sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMClawbackBuilder&
@@ -177,6 +181,7 @@ public:
/**
* @brief Set sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMClawbackBuilder&
@@ -188,6 +193,7 @@ public:
/**
* @brief Set sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMClawbackBuilder&

View File

@@ -21,7 +21,7 @@ class AMMCreateBuilder;
* Type: ttAMM_CREATE (35)
* Delegable: Delegation::delegable
* Amendment: featureAMM
* Privileges: createPseudoAcct
* Privileges: createPseudoAcct | mayCreateMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use AMMCreateBuilder to construct new transactions.
@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfAmount (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfAmount2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -129,6 +131,7 @@ public:
/**
* @brief Set sfAmount (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMCreateBuilder&
@@ -140,6 +143,7 @@ public:
/**
* @brief Set sfAmount2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMCreateBuilder&

View File

@@ -21,7 +21,7 @@ class AMMDeleteBuilder;
* Type: ttAMM_DELETE (40)
* Delegable: Delegation::delegable
* Amendment: featureAMM
* Privileges: mustDeleteAcct
* Privileges: mustDeleteAcct | mayDeleteMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use AMMDeleteBuilder to construct new transactions.
@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -116,6 +118,7 @@ public:
/**
* @brief Set sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMDeleteBuilder&
@@ -127,6 +130,7 @@ public:
/**
* @brief Set sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMDeleteBuilder&

View File

@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -71,6 +73,7 @@ public:
/**
* @brief Get sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -97,6 +100,7 @@ public:
/**
* @brief Get sfAmount2 (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -246,6 +250,7 @@ public:
/**
* @brief Set sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMDepositBuilder&
@@ -257,6 +262,7 @@ public:
/**
* @brief Set sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMDepositBuilder&
@@ -268,6 +274,7 @@ public:
/**
* @brief Set sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMDepositBuilder&
@@ -279,6 +286,7 @@ public:
/**
* @brief Set sfAmount2 (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMDepositBuilder&

View File

@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -129,6 +131,7 @@ public:
/**
* @brief Set sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMVoteBuilder&
@@ -140,6 +143,7 @@ public:
/**
* @brief Set sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMVoteBuilder&

View File

@@ -21,7 +21,7 @@ class AMMWithdrawBuilder;
* Type: ttAMM_WITHDRAW (37)
* Delegable: Delegation::delegable
* Amendment: featureAMM
* Privileges: mayDeleteAcct
* Privileges: mayDeleteAcct | mayAuthorizeMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use AMMWithdrawBuilder to construct new transactions.
@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -71,6 +73,7 @@ public:
/**
* @brief Get sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -97,6 +100,7 @@ public:
/**
* @brief Get sfAmount2 (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -220,6 +224,7 @@ public:
/**
* @brief Set sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMWithdrawBuilder&
@@ -231,6 +236,7 @@ public:
/**
* @brief Set sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMWithdrawBuilder&
@@ -242,6 +248,7 @@ public:
/**
* @brief Set sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMWithdrawBuilder&
@@ -253,6 +260,7 @@ public:
/**
* @brief Set sfAmount2 (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMWithdrawBuilder&

View File

@@ -21,7 +21,7 @@ class CheckCashBuilder;
* Type: ttCHECK_CASH (17)
* Delegable: Delegation::delegable
* Amendment: uint256{}
* Privileges: noPriv
* Privileges: mayCreateMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use CheckCashBuilder to construct new transactions.
@@ -60,6 +60,7 @@ public:
/**
* @brief Get sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -86,6 +87,7 @@ public:
/**
* @brief Get sfDeliverMin (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -166,6 +168,7 @@ public:
/**
* @brief Set sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
CheckCashBuilder&
@@ -177,6 +180,7 @@ public:
/**
* @brief Set sfDeliverMin (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
CheckCashBuilder&

View File

@@ -60,6 +60,7 @@ public:
/**
* @brief Get sfSendMax (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -205,6 +206,7 @@ public:
/**
* @brief Set sfSendMax (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
CheckCreateBuilder&

View File

@@ -0,0 +1,201 @@
// 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 ConfidentialMPTClawbackBuilder;
/**
* @brief Transaction: ConfidentialMPTClawback
*
* Type: ttCONFIDENTIAL_MPT_CLAWBACK (89)
* Delegable: Delegation::delegable
* Amendment: featureConfidentialTransfer
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ConfidentialMPTClawbackBuilder to construct new transactions.
*/
class ConfidentialMPTClawback : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_CLAWBACK;
/**
* @brief Construct a ConfidentialMPTClawback transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ConfidentialMPTClawback(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 ConfidentialMPTClawback");
}
}
// Transaction-specific field getters
/**
* @brief Get sfMPTokenIssuanceID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT192::type::value_type
getMPTokenIssuanceID() const
{
return this->tx_->at(sfMPTokenIssuanceID);
}
/**
* @brief Get sfHolder (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getHolder() const
{
return this->tx_->at(sfHolder);
}
/**
* @brief Get sfMPTAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getMPTAmount() const
{
return this->tx_->at(sfMPTAmount);
}
/**
* @brief Get sfZKProof (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getZKProof() const
{
return this->tx_->at(sfZKProof);
}
};
/**
* @brief Builder for ConfidentialMPTClawback 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 ConfidentialMPTClawbackBuilder : public TransactionBuilderBase<ConfidentialMPTClawbackBuilder>
{
public:
/**
* @brief Construct a new ConfidentialMPTClawbackBuilder with required fields.
* @param account The account initiating the transaction.
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
* @param holder The sfHolder field value.
* @param mPTAmount The sfMPTAmount field value.
* @param zKProof The sfZKProof field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ConfidentialMPTClawbackBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_ACCOUNT::type::value_type> const& holder, std::decay_t<typename SF_UINT64::type::value_type> const& mPTAmount, std::decay_t<typename SF_VL::type::value_type> const& zKProof, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ConfidentialMPTClawbackBuilder>(ttCONFIDENTIAL_MPT_CLAWBACK, account, sequence, fee)
{
setMPTokenIssuanceID(mPTokenIssuanceID);
setHolder(holder);
setMPTAmount(mPTAmount);
setZKProof(zKProof);
}
/**
* @brief Construct a ConfidentialMPTClawbackBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ConfidentialMPTClawbackBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_CLAWBACK)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTClawbackBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfMPTokenIssuanceID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTClawbackBuilder&
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfMPTokenIssuanceID] = value;
return *this;
}
/**
* @brief Set sfHolder (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTClawbackBuilder&
setHolder(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfHolder] = value;
return *this;
}
/**
* @brief Set sfMPTAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTClawbackBuilder&
setMPTAmount(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfMPTAmount] = value;
return *this;
}
/**
* @brief Set sfZKProof (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTClawbackBuilder&
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfZKProof] = value;
return *this;
}
/**
* @brief Build and return the ConfidentialMPTClawback wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ConfidentialMPTClawback
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ConfidentialMPTClawback{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,336 @@
// 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 ConfidentialMPTConvertBuilder;
/**
* @brief Transaction: ConfidentialMPTConvert
*
* Type: ttCONFIDENTIAL_MPT_CONVERT (85)
* Delegable: Delegation::delegable
* Amendment: featureConfidentialTransfer
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ConfidentialMPTConvertBuilder to construct new transactions.
*/
class ConfidentialMPTConvert : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_CONVERT;
/**
* @brief Construct a ConfidentialMPTConvert transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ConfidentialMPTConvert(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 ConfidentialMPTConvert");
}
}
// Transaction-specific field getters
/**
* @brief Get sfMPTokenIssuanceID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT192::type::value_type
getMPTokenIssuanceID() const
{
return this->tx_->at(sfMPTokenIssuanceID);
}
/**
* @brief Get sfMPTAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getMPTAmount() const
{
return this->tx_->at(sfMPTAmount);
}
/**
* @brief Get sfHolderEncryptionKey (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getHolderEncryptionKey() const
{
if (hasHolderEncryptionKey())
{
return this->tx_->at(sfHolderEncryptionKey);
}
return std::nullopt;
}
/**
* @brief Check if sfHolderEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasHolderEncryptionKey() const
{
return this->tx_->isFieldPresent(sfHolderEncryptionKey);
}
/**
* @brief Get sfHolderEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getHolderEncryptedAmount() const
{
return this->tx_->at(sfHolderEncryptedAmount);
}
/**
* @brief Get sfIssuerEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getIssuerEncryptedAmount() const
{
return this->tx_->at(sfIssuerEncryptedAmount);
}
/**
* @brief Get sfAuditorEncryptedAmount (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptedAmount() const
{
if (hasAuditorEncryptedAmount())
{
return this->tx_->at(sfAuditorEncryptedAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptedAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptedAmount() const
{
return this->tx_->isFieldPresent(sfAuditorEncryptedAmount);
}
/**
* @brief Get sfBlindingFactor (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getBlindingFactor() const
{
return this->tx_->at(sfBlindingFactor);
}
/**
* @brief Get sfZKProof (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getZKProof() const
{
if (hasZKProof())
{
return this->tx_->at(sfZKProof);
}
return std::nullopt;
}
/**
* @brief Check if sfZKProof is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasZKProof() const
{
return this->tx_->isFieldPresent(sfZKProof);
}
};
/**
* @brief Builder for ConfidentialMPTConvert 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 ConfidentialMPTConvertBuilder : public TransactionBuilderBase<ConfidentialMPTConvertBuilder>
{
public:
/**
* @brief Construct a new ConfidentialMPTConvertBuilder with required fields.
* @param account The account initiating the transaction.
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
* @param mPTAmount The sfMPTAmount field value.
* @param holderEncryptedAmount The sfHolderEncryptedAmount field value.
* @param issuerEncryptedAmount The sfIssuerEncryptedAmount field value.
* @param blindingFactor The sfBlindingFactor field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ConfidentialMPTConvertBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_UINT64::type::value_type> const& mPTAmount, std::decay_t<typename SF_VL::type::value_type> const& holderEncryptedAmount, std::decay_t<typename SF_VL::type::value_type> const& issuerEncryptedAmount, std::decay_t<typename SF_UINT256::type::value_type> const& blindingFactor, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ConfidentialMPTConvertBuilder>(ttCONFIDENTIAL_MPT_CONVERT, account, sequence, fee)
{
setMPTokenIssuanceID(mPTokenIssuanceID);
setMPTAmount(mPTAmount);
setHolderEncryptedAmount(holderEncryptedAmount);
setIssuerEncryptedAmount(issuerEncryptedAmount);
setBlindingFactor(blindingFactor);
}
/**
* @brief Construct a ConfidentialMPTConvertBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ConfidentialMPTConvertBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_CONVERT)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTConvertBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfMPTokenIssuanceID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfMPTokenIssuanceID] = value;
return *this;
}
/**
* @brief Set sfMPTAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setMPTAmount(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfMPTAmount] = value;
return *this;
}
/**
* @brief Set sfHolderEncryptionKey (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setHolderEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfHolderEncryptionKey] = value;
return *this;
}
/**
* @brief Set sfHolderEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setHolderEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfHolderEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfIssuerEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setIssuerEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptedAmount (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setAuditorEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfBlindingFactor (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setBlindingFactor(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfBlindingFactor] = value;
return *this;
}
/**
* @brief Set sfZKProof (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfZKProof] = value;
return *this;
}
/**
* @brief Build and return the ConfidentialMPTConvert wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ConfidentialMPTConvert
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ConfidentialMPTConvert{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,310 @@
// 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 ConfidentialMPTConvertBackBuilder;
/**
* @brief Transaction: ConfidentialMPTConvertBack
*
* Type: ttCONFIDENTIAL_MPT_CONVERT_BACK (87)
* Delegable: Delegation::delegable
* Amendment: featureConfidentialTransfer
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ConfidentialMPTConvertBackBuilder to construct new transactions.
*/
class ConfidentialMPTConvertBack : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_CONVERT_BACK;
/**
* @brief Construct a ConfidentialMPTConvertBack transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ConfidentialMPTConvertBack(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 ConfidentialMPTConvertBack");
}
}
// Transaction-specific field getters
/**
* @brief Get sfMPTokenIssuanceID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT192::type::value_type
getMPTokenIssuanceID() const
{
return this->tx_->at(sfMPTokenIssuanceID);
}
/**
* @brief Get sfMPTAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getMPTAmount() const
{
return this->tx_->at(sfMPTAmount);
}
/**
* @brief Get sfHolderEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getHolderEncryptedAmount() const
{
return this->tx_->at(sfHolderEncryptedAmount);
}
/**
* @brief Get sfIssuerEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getIssuerEncryptedAmount() const
{
return this->tx_->at(sfIssuerEncryptedAmount);
}
/**
* @brief Get sfAuditorEncryptedAmount (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptedAmount() const
{
if (hasAuditorEncryptedAmount())
{
return this->tx_->at(sfAuditorEncryptedAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptedAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptedAmount() const
{
return this->tx_->isFieldPresent(sfAuditorEncryptedAmount);
}
/**
* @brief Get sfBlindingFactor (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getBlindingFactor() const
{
return this->tx_->at(sfBlindingFactor);
}
/**
* @brief Get sfZKProof (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getZKProof() const
{
return this->tx_->at(sfZKProof);
}
/**
* @brief Get sfBalanceCommitment (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getBalanceCommitment() const
{
return this->tx_->at(sfBalanceCommitment);
}
};
/**
* @brief Builder for ConfidentialMPTConvertBack 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 ConfidentialMPTConvertBackBuilder : public TransactionBuilderBase<ConfidentialMPTConvertBackBuilder>
{
public:
/**
* @brief Construct a new ConfidentialMPTConvertBackBuilder with required fields.
* @param account The account initiating the transaction.
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
* @param mPTAmount The sfMPTAmount field value.
* @param holderEncryptedAmount The sfHolderEncryptedAmount field value.
* @param issuerEncryptedAmount The sfIssuerEncryptedAmount field value.
* @param blindingFactor The sfBlindingFactor field value.
* @param zKProof The sfZKProof field value.
* @param balanceCommitment The sfBalanceCommitment field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ConfidentialMPTConvertBackBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_UINT64::type::value_type> const& mPTAmount, std::decay_t<typename SF_VL::type::value_type> const& holderEncryptedAmount, std::decay_t<typename SF_VL::type::value_type> const& issuerEncryptedAmount, std::decay_t<typename SF_UINT256::type::value_type> const& blindingFactor, std::decay_t<typename SF_VL::type::value_type> const& zKProof, std::decay_t<typename SF_VL::type::value_type> const& balanceCommitment, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ConfidentialMPTConvertBackBuilder>(ttCONFIDENTIAL_MPT_CONVERT_BACK, account, sequence, fee)
{
setMPTokenIssuanceID(mPTokenIssuanceID);
setMPTAmount(mPTAmount);
setHolderEncryptedAmount(holderEncryptedAmount);
setIssuerEncryptedAmount(issuerEncryptedAmount);
setBlindingFactor(blindingFactor);
setZKProof(zKProof);
setBalanceCommitment(balanceCommitment);
}
/**
* @brief Construct a ConfidentialMPTConvertBackBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ConfidentialMPTConvertBackBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_CONVERT_BACK)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTConvertBackBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfMPTokenIssuanceID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfMPTokenIssuanceID] = value;
return *this;
}
/**
* @brief Set sfMPTAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setMPTAmount(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfMPTAmount] = value;
return *this;
}
/**
* @brief Set sfHolderEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setHolderEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfHolderEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfIssuerEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setIssuerEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptedAmount (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setAuditorEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfBlindingFactor (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setBlindingFactor(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfBlindingFactor] = value;
return *this;
}
/**
* @brief Set sfZKProof (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfZKProof] = value;
return *this;
}
/**
* @brief Set sfBalanceCommitment (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setBalanceCommitment(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfBalanceCommitment] = value;
return *this;
}
/**
* @brief Build and return the ConfidentialMPTConvertBack wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ConfidentialMPTConvertBack
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ConfidentialMPTConvertBack{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 ConfidentialMPTMergeInboxBuilder;
/**
* @brief Transaction: ConfidentialMPTMergeInbox
*
* Type: ttCONFIDENTIAL_MPT_MERGE_INBOX (86)
* Delegable: Delegation::delegable
* Amendment: featureConfidentialTransfer
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ConfidentialMPTMergeInboxBuilder to construct new transactions.
*/
class ConfidentialMPTMergeInbox : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_MERGE_INBOX;
/**
* @brief Construct a ConfidentialMPTMergeInbox transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ConfidentialMPTMergeInbox(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 ConfidentialMPTMergeInbox");
}
}
// Transaction-specific field getters
/**
* @brief Get sfMPTokenIssuanceID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT192::type::value_type
getMPTokenIssuanceID() const
{
return this->tx_->at(sfMPTokenIssuanceID);
}
};
/**
* @brief Builder for ConfidentialMPTMergeInbox 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 ConfidentialMPTMergeInboxBuilder : public TransactionBuilderBase<ConfidentialMPTMergeInboxBuilder>
{
public:
/**
* @brief Construct a new ConfidentialMPTMergeInboxBuilder with required fields.
* @param account The account initiating the transaction.
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ConfidentialMPTMergeInboxBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ConfidentialMPTMergeInboxBuilder>(ttCONFIDENTIAL_MPT_MERGE_INBOX, account, sequence, fee)
{
setMPTokenIssuanceID(mPTokenIssuanceID);
}
/**
* @brief Construct a ConfidentialMPTMergeInboxBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ConfidentialMPTMergeInboxBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_MERGE_INBOX)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTMergeInboxBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfMPTokenIssuanceID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTMergeInboxBuilder&
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfMPTokenIssuanceID] = value;
return *this;
}
/**
* @brief Build and return the ConfidentialMPTMergeInbox wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ConfidentialMPTMergeInbox
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ConfidentialMPTMergeInbox{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,371 @@
// 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 ConfidentialMPTSendBuilder;
/**
* @brief Transaction: ConfidentialMPTSend
*
* Type: ttCONFIDENTIAL_MPT_SEND (88)
* Delegable: Delegation::delegable
* Amendment: featureConfidentialTransfer
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ConfidentialMPTSendBuilder to construct new transactions.
*/
class ConfidentialMPTSend : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_SEND;
/**
* @brief Construct a ConfidentialMPTSend transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ConfidentialMPTSend(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 ConfidentialMPTSend");
}
}
// Transaction-specific field getters
/**
* @brief Get sfMPTokenIssuanceID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT192::type::value_type
getMPTokenIssuanceID() const
{
return this->tx_->at(sfMPTokenIssuanceID);
}
/**
* @brief Get sfDestination (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getDestination() const
{
return this->tx_->at(sfDestination);
}
/**
* @brief Get sfSenderEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getSenderEncryptedAmount() const
{
return this->tx_->at(sfSenderEncryptedAmount);
}
/**
* @brief Get sfDestinationEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getDestinationEncryptedAmount() const
{
return this->tx_->at(sfDestinationEncryptedAmount);
}
/**
* @brief Get sfIssuerEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getIssuerEncryptedAmount() const
{
return this->tx_->at(sfIssuerEncryptedAmount);
}
/**
* @brief Get sfAuditorEncryptedAmount (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptedAmount() const
{
if (hasAuditorEncryptedAmount())
{
return this->tx_->at(sfAuditorEncryptedAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptedAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptedAmount() const
{
return this->tx_->isFieldPresent(sfAuditorEncryptedAmount);
}
/**
* @brief Get sfZKProof (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getZKProof() const
{
return this->tx_->at(sfZKProof);
}
/**
* @brief Get sfAmountCommitment (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getAmountCommitment() const
{
return this->tx_->at(sfAmountCommitment);
}
/**
* @brief Get sfBalanceCommitment (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getBalanceCommitment() const
{
return this->tx_->at(sfBalanceCommitment);
}
/**
* @brief Get sfCredentialIDs (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VECTOR256::type::value_type>
getCredentialIDs() const
{
if (hasCredentialIDs())
{
return this->tx_->at(sfCredentialIDs);
}
return std::nullopt;
}
/**
* @brief Check if sfCredentialIDs is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasCredentialIDs() const
{
return this->tx_->isFieldPresent(sfCredentialIDs);
}
};
/**
* @brief Builder for ConfidentialMPTSend 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 ConfidentialMPTSendBuilder : public TransactionBuilderBase<ConfidentialMPTSendBuilder>
{
public:
/**
* @brief Construct a new ConfidentialMPTSendBuilder with required fields.
* @param account The account initiating the transaction.
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
* @param destination The sfDestination field value.
* @param senderEncryptedAmount The sfSenderEncryptedAmount field value.
* @param destinationEncryptedAmount The sfDestinationEncryptedAmount field value.
* @param issuerEncryptedAmount The sfIssuerEncryptedAmount field value.
* @param zKProof The sfZKProof field value.
* @param amountCommitment The sfAmountCommitment field value.
* @param balanceCommitment The sfBalanceCommitment field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ConfidentialMPTSendBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_ACCOUNT::type::value_type> const& destination, std::decay_t<typename SF_VL::type::value_type> const& senderEncryptedAmount, std::decay_t<typename SF_VL::type::value_type> const& destinationEncryptedAmount, std::decay_t<typename SF_VL::type::value_type> const& issuerEncryptedAmount, std::decay_t<typename SF_VL::type::value_type> const& zKProof, std::decay_t<typename SF_VL::type::value_type> const& amountCommitment, std::decay_t<typename SF_VL::type::value_type> const& balanceCommitment, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ConfidentialMPTSendBuilder>(ttCONFIDENTIAL_MPT_SEND, account, sequence, fee)
{
setMPTokenIssuanceID(mPTokenIssuanceID);
setDestination(destination);
setSenderEncryptedAmount(senderEncryptedAmount);
setDestinationEncryptedAmount(destinationEncryptedAmount);
setIssuerEncryptedAmount(issuerEncryptedAmount);
setZKProof(zKProof);
setAmountCommitment(amountCommitment);
setBalanceCommitment(balanceCommitment);
}
/**
* @brief Construct a ConfidentialMPTSendBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ConfidentialMPTSendBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_SEND)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTSendBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfMPTokenIssuanceID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfMPTokenIssuanceID] = value;
return *this;
}
/**
* @brief Set sfDestination (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setDestination(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfDestination] = value;
return *this;
}
/**
* @brief Set sfSenderEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setSenderEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfSenderEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfDestinationEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setDestinationEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfDestinationEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfIssuerEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setIssuerEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptedAmount (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setAuditorEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfZKProof (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfZKProof] = value;
return *this;
}
/**
* @brief Set sfAmountCommitment (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setAmountCommitment(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAmountCommitment] = value;
return *this;
}
/**
* @brief Set sfBalanceCommitment (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setBalanceCommitment(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfBalanceCommitment] = value;
return *this;
}
/**
* @brief Set sfCredentialIDs (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setCredentialIDs(std::decay_t<typename SF_VECTOR256::type::value_type> const& value)
{
object_[sfCredentialIDs] = value;
return *this;
}
/**
* @brief Build and return the ConfidentialMPTSend wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ConfidentialMPTSend
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ConfidentialMPTSend{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -187,6 +187,58 @@ public:
{
return this->tx_->isFieldPresent(sfMutableFlags);
}
/**
* @brief Get sfIssuerEncryptionKey (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getIssuerEncryptionKey() const
{
if (hasIssuerEncryptionKey())
{
return this->tx_->at(sfIssuerEncryptionKey);
}
return std::nullopt;
}
/**
* @brief Check if sfIssuerEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerEncryptionKey() const
{
return this->tx_->isFieldPresent(sfIssuerEncryptionKey);
}
/**
* @brief Get sfAuditorEncryptionKey (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptionKey() const
{
if (hasAuditorEncryptionKey())
{
return this->tx_->at(sfAuditorEncryptionKey);
}
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptionKey() const
{
return this->tx_->isFieldPresent(sfAuditorEncryptionKey);
}
};
/**
@@ -297,6 +349,28 @@ public:
return *this;
}
/**
* @brief Set sfIssuerEncryptionKey (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceSetBuilder&
setIssuerEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptionKey] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptionKey (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceSetBuilder&
setAuditorEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptionKey] = value;
return *this;
}
/**
* @brief Build and return the MPTokenIssuanceSet wrapper.
* @param publicKey The public key for signing.

View File

@@ -21,7 +21,7 @@ class OfferCreateBuilder;
* Type: ttOFFER_CREATE (7)
* Delegable: Delegation::delegable
* Amendment: uint256{}
* Privileges: noPriv
* Privileges: mayCreateMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use OfferCreateBuilder to construct new transactions.
@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfTakerPays (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfTakerGets (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -194,6 +196,7 @@ public:
/**
* @brief Set sfTakerPays (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
OfferCreateBuilder&
@@ -205,6 +208,7 @@ public:
/**
* @brief Set sfTakerGets (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
OfferCreateBuilder&

View File

@@ -21,7 +21,7 @@ class PaymentBuilder;
* Type: ttPAYMENT (0)
* Delegable: Delegation::delegable
* Amendment: uint256{}
* Privileges: createAcct
* Privileges: createAcct | mayCreateMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use PaymentBuilder to construct new transactions.

View File

@@ -398,7 +398,9 @@ using InvariantChecks = std::tuple<
ValidPseudoAccounts,
ValidLoanBroker,
ValidLoan,
ValidVault>;
ValidVault,
ValidConfidentialMPToken,
ValidMPTPayment>;
/**
* @brief get a tuple of all invariant checks

View File

@@ -44,6 +44,7 @@ enum Privilege {
mayDeleteMPT = 0x0400, // The transaction MAY delete an MPT object. May not create.
mustModifyVault = 0x0800, // The transaction must modify, delete or create, a vault
mayModifyVault = 0x1000, // The transaction MAY modify, delete or create, a vault
mayCreateMPT = 0x2000, // The transaction MAY create an MPT object, except for issuer.
};
constexpr Privilege

View File

@@ -28,4 +28,75 @@ public:
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
};
/** Verify:
* - OutstandingAmount <= MaximumAmount for any MPT
* - OutstandingAmount after = OutstandingAmount before +
* sum (MPT after - MPT before) - this is total MPT credit/debit
*/
class ValidMPTPayment
{
enum Order { Before = 0, After = 1 };
struct MPTData
{
std::array<std::int64_t, After + 1> outstanding{};
// sum (MPT after - MPT before)
std::int64_t mptAmount{0};
};
// true if OutstandingAmount > MaximumAmount in after for any MPT
bool overflow_{false};
// mptid:MPTData
hash_map<uint192, MPTData> data_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariants: Confidential MPToken consistency
*
* - Convert/ConvertBack symmetry:
* Regular MPToken balance change (±X) == COA (Confidential Outstanding Amount) change (∓X)
* - Cannot delete MPToken with non-zero confidential state:
* Cannot delete if sfIssuerEncryptedBalance exists
* Cannot delete if sfConfidentialBalanceInbox and sfConfidentialBalanceSpending exist
* - Privacy flag consistency:
* MPToken can only have encrypted fields if lsfMPTCanConfidentialAmount is set on
* issuance.
* - Encrypted field existence consistency:
* If sfConfidentialBalanceSpending/sfConfidentialBalanceInbox exists, then
* sfIssuerEncryptedBalance must also exist (and vice versa).
* - COA <= OutstandingAmount:
* Confidential outstanding balance cannot exceed total outstanding.
* - Verifies sfConfidentialBalanceVersion is changed whenever sfConfidentialBalanceSpending is
* modified on an MPToken.
*/
class ValidConfidentialMPToken
{
struct Changes
{
std::int64_t mptAmountDelta = 0;
std::int64_t coaDelta = 0;
std::int64_t outstandingDelta = 0;
SLE::const_pointer issuance;
bool deletedWithEncrypted = false;
bool badConsistency = false;
bool badCOA = false;
bool requiresPrivacyFlag = false;
bool badVersion = false;
};
std::map<uint192, Changes> changes_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
} // namespace xrpl

View File

@@ -4,13 +4,13 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/AMMUtils.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/tx/transactors/dex/AMMContext.h>
namespace xrpl {
template <typename TIn, typename TOut>
template <StepAmount TIn, StepAmount TOut>
class AMMOffer;
/** AMMLiquidity class provides AMM offers to BookStep class.
@@ -35,8 +35,8 @@ private:
AMMContext& ammContext_;
AccountID const ammAccountID_;
std::uint32_t const tradingFee_;
Issue const issueIn_;
Issue const issueOut_;
Asset const assetIn_;
Asset const assetOut_;
// Initial AMM pool balances
TAmounts<TIn, TOut> const initialBalances_;
beast::Journal const j_;
@@ -46,8 +46,8 @@ public:
ReadView const& view,
AccountID const& ammAccountID,
std::uint32_t tradingFee,
Issue const& in,
Issue const& out,
Asset const& in,
Asset const& out,
AMMContext& ammContext,
beast::Journal j);
~AMMLiquidity() = default;
@@ -87,16 +87,16 @@ public:
return ammContext_;
}
Issue const&
issueIn() const
Asset const&
assetIn() const
{
return issueIn_;
return assetIn_;
}
Issue const&
issueOut() const
Asset const&
assetOut() const
{
return issueOut_;
return assetOut_;
}
private:

View File

@@ -3,6 +3,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/TER.h>
@@ -16,7 +17,7 @@ class QualityFunction;
* methods for use in generic BookStep methods. AMMOffer amounts
* are changed indirectly in BookStep limiting steps.
*/
template <typename TIn, typename TOut>
template <StepAmount TIn, StepAmount TOut>
class AMMOffer
{
private:
@@ -52,8 +53,11 @@ public:
return quality_;
}
Issue const&
issueIn() const;
Asset const&
assetIn() const;
Asset const&
assetOut() const;
AccountID const&
owner() const;
@@ -99,7 +103,8 @@ public:
static TER
send(Args&&... args)
{
return accountSend(std::forward<Args>(args)..., WaiveTransferFee::Yes);
return accountSend(
std::forward<Args>(args)..., WaiveTransferFee::Yes, AllowMPTOverflow::Yes);
}
bool

View File

@@ -3,6 +3,8 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
@@ -12,28 +14,15 @@
namespace xrpl {
template <class TIn, class TOut>
class TOfferBase
{
protected:
Issue issIn_;
Issue issOut_;
};
template <>
class TOfferBase<STAmount, STAmount>
{
public:
explicit TOfferBase() = default;
};
template <class TIn = STAmount, class TOut = STAmount>
class TOffer : private TOfferBase<TIn, TOut>
template <StepAmount TIn, StepAmount TOut>
class TOffer
{
private:
SLE::pointer m_entry;
Quality m_quality{};
AccountID m_account;
Asset assetIn_;
Asset assetOut_;
TAmounts<TIn, TOut> m_amounts{};
void
@@ -113,10 +102,10 @@ public:
return m_entry->key();
}
Issue const&
issueIn() const;
Issue const&
issueOut() const;
Asset const&
assetIn() const;
Asset const&
assetOut() const;
TAmounts<TIn, TOut>
limitOut(TAmounts<TIn, TOut> const& offerAmount, TOut const& limit, bool roundUp) const;
@@ -131,8 +120,8 @@ public:
bool
isFunded() const
{
// Offer owner is issuer; they have unlimited funds
return m_account == issueOut().account;
// Offer owner is issuer; they have unlimited funds if IOU
return m_account == assetOut_.getIssuer() && assetOut_.holds<Issue>();
}
static std::pair<std::uint32_t, std::uint32_t>
@@ -167,9 +156,7 @@ public:
}
};
using Offer = TOffer<>;
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
TOffer<TIn, TOut>::TOffer(SLE::pointer const& entry, Quality quality)
: m_entry(entry), m_quality(quality), m_account(m_entry->getAccountID(sfAccount))
{
@@ -177,33 +164,26 @@ TOffer<TIn, TOut>::TOffer(SLE::pointer const& entry, Quality quality)
auto const tg = m_entry->getFieldAmount(sfTakerGets);
m_amounts.in = toAmount<TIn>(tp);
m_amounts.out = toAmount<TOut>(tg);
this->issIn_ = tp.issue();
this->issOut_ = tg.issue();
assetIn_ = tp.asset();
assetOut_ = tg.asset();
}
template <>
inline TOffer<STAmount, STAmount>::TOffer(SLE::pointer const& entry, Quality quality)
: m_entry(entry)
, m_quality(quality)
, m_account(m_entry->getAccountID(sfAccount))
, m_amounts(m_entry->getFieldAmount(sfTakerPays), m_entry->getFieldAmount(sfTakerGets))
{
}
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
void
TOffer<TIn, TOut>::setFieldAmounts()
{
// LCOV_EXCL_START
#ifdef _MSC_VER
UNREACHABLE("xrpl::TOffer::setFieldAmounts : must be specialized");
#else
static_assert(sizeof(TOut) == -1, "Must be specialized");
#endif
// LCOV_EXCL_STOP
if constexpr (std::is_same_v<TIn, XRPAmount>)
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in));
else
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, assetIn_));
if constexpr (std::is_same_v<TOut, XRPAmount>)
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out));
else
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, assetOut_));
}
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
TAmounts<TIn, TOut>
TOffer<TIn, TOut>::limitOut(TAmounts<TIn, TOut> const& offerAmount, TOut const& limit, bool roundUp)
const
@@ -213,7 +193,7 @@ TOffer<TIn, TOut>::limitOut(TAmounts<TIn, TOut> const& offerAmount, TOut const&
return quality().ceil_out_strict(offerAmount, limit, roundUp);
}
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
TAmounts<TIn, TOut>
TOffer<TIn, TOut>::limitIn(TAmounts<TIn, TOut> const& offerAmount, TIn const& limit, bool roundUp)
const
@@ -228,75 +208,29 @@ TOffer<TIn, TOut>::limitIn(TAmounts<TIn, TOut> const& offerAmount, TIn const& li
return m_quality.ceil_in(offerAmount, limit);
}
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
template <typename... Args>
TER
TOffer<TIn, TOut>::send(Args&&... args)
{
return accountSend(std::forward<Args>(args)...);
return accountSend(std::forward<Args>(args)..., WaiveTransferFee::No, AllowMPTOverflow::Yes);
}
template <>
inline void
TOffer<STAmount, STAmount>::setFieldAmounts()
template <StepAmount TIn, StepAmount TOut>
Asset const&
TOffer<TIn, TOut>::assetIn() const
{
m_entry->setFieldAmount(sfTakerPays, m_amounts.in);
m_entry->setFieldAmount(sfTakerGets, m_amounts.out);
return assetIn_;
}
template <>
inline void
TOffer<IOUAmount, IOUAmount>::setFieldAmounts()
template <StepAmount TIn, StepAmount TOut>
Asset const&
TOffer<TIn, TOut>::assetOut() const
{
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_));
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_));
return assetOut_;
}
template <>
inline void
TOffer<IOUAmount, XRPAmount>::setFieldAmounts()
{
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_));
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out));
}
template <>
inline void
TOffer<XRPAmount, IOUAmount>::setFieldAmounts()
{
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in));
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_));
}
template <class TIn, class TOut>
Issue const&
TOffer<TIn, TOut>::issueIn() const
{
return this->issIn_;
}
template <>
inline Issue const&
TOffer<STAmount, STAmount>::issueIn() const
{
return m_amounts.in.issue();
}
template <class TIn, class TOut>
Issue const&
TOffer<TIn, TOut>::issueOut() const
{
return this->issOut_;
}
template <>
inline Issue const&
TOffer<STAmount, STAmount>::issueOut() const
{
return m_amounts.out.issue();
}
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
inline std::ostream&
operator<<(std::ostream& os, TOffer<TIn, TOut> const& offer)
{

View File

@@ -4,6 +4,7 @@
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/tx/paths/BookTip.h>
#include <xrpl/tx/paths/Offer.h>
@@ -11,7 +12,7 @@
namespace xrpl {
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
class TOfferStreamBase
{
public:
@@ -64,6 +65,7 @@ protected:
permRmOffer(uint256 const& offerIndex) = 0;
template <class TTakerPays, class TTakerGets>
requires ValidTaker<TTakerPays, TTakerGets>
bool
shouldRmSmallIncreasedQOffer() const;
@@ -105,33 +107,6 @@ public:
}
};
/** Presents and consumes the offers in an order book.
Two `ApplyView` objects accumulate changes to the ledger. `view`
is applied when the calling transaction succeeds. If the calling
transaction fails, then `view_cancel` is applied.
Certain invalid offers are automatically removed:
- Offers with missing ledger entries
- Offers that expired
- Offers found unfunded:
An offer is found unfunded when the corresponding balance is zero
and the caller has not modified the balance. This is accomplished
by also looking up the balance in the cancel view.
When an offer is removed, it is removed from both views. This grooms the
order book regardless of whether or not the transaction is successful.
*/
class OfferStream : public TOfferStreamBase<STAmount, STAmount>
{
protected:
void
permRmOffer(uint256 const& offerIndex) override;
public:
using TOfferStreamBase<STAmount, STAmount>::TOfferStreamBase;
};
/** Presents and consumes the offers in an order book.
The `view_' ` `ApplyView` accumulates changes to the ledger.
@@ -149,7 +124,7 @@ public:
and the caller has not modified the balance. This is accomplished
by also looking up the balance in the cancel view.
*/
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
class FlowOfferStream : public TOfferStreamBase<TIn, TOut>
{
private:

View File

@@ -1,198 +0,0 @@
#pragma once
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/XRPAmount.h>
#include <optional>
namespace xrpl {
struct AmountSpec
{
explicit AmountSpec() = default;
bool native{};
union
{
XRPAmount xrp;
IOUAmount iou = {};
};
std::optional<AccountID> issuer;
std::optional<Currency> currency;
friend std::ostream&
operator<<(std::ostream& stream, AmountSpec const& amt)
{
if (amt.native)
stream << to_string(amt.xrp);
else
stream << to_string(amt.iou);
if (amt.currency)
stream << "/(" << *amt.currency << ")";
if (amt.issuer)
stream << "/" << *amt.issuer << "";
return stream;
}
};
struct EitherAmount
{
#ifndef NDEBUG
bool native = false;
#endif
union
{
IOUAmount iou = {};
XRPAmount xrp;
};
EitherAmount() = default;
explicit EitherAmount(IOUAmount const& a) : iou(a)
{
}
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
// ignore warning about half of iou amount being uninitialized
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
explicit EitherAmount(XRPAmount const& a) : xrp(a)
{
#ifndef NDEBUG
native = true;
#endif
}
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
explicit EitherAmount(AmountSpec const& a)
{
#ifndef NDEBUG
native = a.native;
#endif
if (a.native)
xrp = a.xrp;
else
iou = a.iou;
}
#ifndef NDEBUG
friend std::ostream&
operator<<(std::ostream& stream, EitherAmount const& amt)
{
if (amt.native)
stream << to_string(amt.xrp);
else
stream << to_string(amt.iou);
return stream;
}
#endif
};
template <class T>
T&
get(EitherAmount& amt)
{
static_assert(sizeof(T) == -1, "Must used specialized function");
return T(0);
}
template <>
inline IOUAmount&
get<IOUAmount>(EitherAmount& amt)
{
XRPL_ASSERT(!amt.native, "xrpl::get<IOUAmount>(EitherAmount&) : is not XRP");
return amt.iou;
}
template <>
inline XRPAmount&
get<XRPAmount>(EitherAmount& amt)
{
XRPL_ASSERT(amt.native, "xrpl::get<XRPAmount>(EitherAmount&) : is XRP");
return amt.xrp;
}
template <class T>
T const&
get(EitherAmount const& amt)
{
static_assert(sizeof(T) == -1, "Must used specialized function");
return T(0);
}
template <>
inline IOUAmount const&
get<IOUAmount>(EitherAmount const& amt)
{
XRPL_ASSERT(!amt.native, "xrpl::get<IOUAmount>(EitherAmount const&) : is not XRP");
return amt.iou;
}
template <>
inline XRPAmount const&
get<XRPAmount>(EitherAmount const& amt)
{
XRPL_ASSERT(amt.native, "xrpl::get<XRPAmount>(EitherAmount const&) : is XRP");
return amt.xrp;
}
inline AmountSpec
toAmountSpec(STAmount const& amt)
{
XRPL_ASSERT(
amt.mantissa() < std::numeric_limits<std::int64_t>::max(),
"xrpl::toAmountSpec(STAmount const&) : maximum mantissa");
bool const isNeg = amt.negative();
std::int64_t const sMant = isNeg ? -std::int64_t(amt.mantissa()) : amt.mantissa();
AmountSpec result;
result.native = isXRP(amt);
if (result.native)
{
result.xrp = XRPAmount(sMant);
}
else
{
result.iou = IOUAmount(sMant, amt.exponent());
result.issuer = amt.issue().account;
result.currency = amt.issue().currency;
}
return result;
}
inline EitherAmount
toEitherAmount(STAmount const& amt)
{
if (isXRP(amt))
return EitherAmount{amt.xrp()};
return EitherAmount{amt.iou()};
}
inline AmountSpec
toAmountSpec(EitherAmount const& ea, std::optional<Currency> const& c)
{
AmountSpec r;
r.native = (!c || isXRP(*c));
r.currency = c;
XRPL_ASSERT(
ea.native == r.native,
"xrpl::toAmountSpec(EitherAmount const&&, std::optional<Currency>) : "
"matching native");
if (r.native)
{
r.xrp = ea.xrp;
}
else
{
r.iou = ea.iou;
}
return r;
}
} // namespace xrpl

View File

@@ -0,0 +1,54 @@
#pragma once
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/XRPAmount.h>
namespace xrpl {
struct EitherAmount
{
std::variant<XRPAmount, IOUAmount, MPTAmount> amount;
explicit EitherAmount() = default;
template <StepAmount T>
explicit EitherAmount(T const& a) : amount(a)
{
}
template <StepAmount T>
[[nodiscard]] bool
holds() const
{
return std::holds_alternative<T>(amount);
}
template <StepAmount T>
[[nodiscard]] T const&
get() const
{
if (!holds<T>())
Throw<std::logic_error>("EitherAmount doesn't hold requested amount");
return std::get<T>(amount);
}
#ifndef NDEBUG
friend std::ostream&
operator<<(std::ostream& stream, EitherAmount const& amt)
{
std::visit([&]<StepAmount T>(T const& a) { stream << to_string(a); }, amt.amount);
return stream;
}
#endif
};
template <StepAmount T>
T const&
get(EitherAmount const& amt)
{
return amt.get<T>();
}
} // namespace xrpl

View File

@@ -208,14 +208,14 @@ struct FlowDebugInfo
auto writeXrpAmtList = [&write_list](
std::vector<EitherAmount> const& amts, char delim = ';') {
auto get_val = [](EitherAmount const& a) -> std::string {
return xrpl::to_string(a.xrp);
return xrpl::to_string(a.get<XRPAmount>());
};
write_list(amts, get_val, delim);
};
auto writeIouAmtList = [&write_list](
std::vector<EitherAmount> const& amts, char delim = ';') {
auto get_val = [](EitherAmount const& a) -> std::string {
return xrpl::to_string(a.iou);
return xrpl::to_string(a.get<IOUAmount>());
};
write_list(amts, get_val, delim);
};

View File

@@ -50,8 +50,7 @@ checkFreeze(
if (!sleAmm)
return tecINTERNAL; // LCOV_EXCL_LINE
if (isLPTokenFrozen(
view, src, (*sleAmm)[sfAsset].get<Issue>(), (*sleAmm)[sfAsset2].get<Issue>()))
if (isLPTokenFrozen(view, src, (*sleAmm)[sfAsset], (*sleAmm)[sfAsset2]))
{
return terNO_LINE;
}

View File

@@ -2,11 +2,11 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/QualityFunction.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/paths/detail/AmountSpec.h>
#include <xrpl/tx/paths/detail/EitherAmount.h>
#include <boost/container/flat_set.hpp>
@@ -44,6 +44,7 @@ issues(DebtDirection dir)
BookStepIX is an IOU/XRP offer book
BookStepXI is an XRP/IOU offer book
XRPEndpointStep is the source or destination account for XRP
MPTEndpointStep is the source or destination account for MPT
Amounts may be transformed through a step in either the forward or the
reverse direction. In the forward direction, the function `fwd` is used to
@@ -339,8 +340,8 @@ std::pair<TER, STPath>
normalizePath(
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
std::optional<Issue> const& sendMaxIssue,
Asset const& deliver,
std::optional<Asset> const& sendMaxAsset,
STPath const& path);
/**
@@ -355,7 +356,7 @@ normalizePath(
optimization. If, during direct offer crossing, the
quality of the tip of the book drops below this value,
then evaluating the strand can stop.
@param sendMaxIssue Optional asset to send.
@param sendMaxAsset Optional asset to send.
@param path Liquidity sources to use for this strand of the payment. The path
contains an ordered collection of the offer books to use and
accounts to ripple through.
@@ -372,9 +373,9 @@ toStrand(
ReadView const& sb,
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
Asset const& deliver,
std::optional<Quality> const& limitQuality,
std::optional<Issue> const& sendMaxIssue,
std::optional<Asset> const& sendMaxAsset,
STPath const& path,
bool ownerPaysTransferFee,
OfferCrossing offerCrossing,
@@ -413,9 +414,9 @@ toStrands(
ReadView const& sb,
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
Asset const& deliver,
std::optional<Quality> const& limitQuality,
std::optional<Issue> const& sendMax,
std::optional<Asset> const& sendMax,
STPathSet const& paths,
bool addDefaultPath,
bool ownerPaysTransferFee,
@@ -425,7 +426,7 @@ toStrands(
beast::Journal j);
/// @cond INTERNAL
template <class TIn, class TOut, class TDerived>
template <StepAmount TIn, StepAmount TOut, class TDerived>
struct StepImp : public Step
{
explicit StepImp() = default;
@@ -493,8 +494,16 @@ public:
// Check equal with tolerance
bool
checkNear(IOUAmount const& expected, IOUAmount const& actual);
bool
checkNear(XRPAmount const& expected, XRPAmount const& actual);
inline bool
checkNear(MPTAmount const& expected, MPTAmount const& actual)
{
return expected == actual;
}
inline bool
checkNear(XRPAmount const& expected, XRPAmount const& actual)
{
return expected == actual;
}
/// @endcond
/**
@@ -505,7 +514,7 @@ struct StrandContext
ReadView const& view; ///< Current ReadView
AccountID const strandSrc; ///< Strand source account
AccountID const strandDst; ///< Strand destination account
Issue const strandDeliver; ///< Issue strand delivers
Asset const strandDeliver; ///< Asset strand delivers
std::optional<Quality> const limitQuality; ///< Worst accepted quality
bool const isFirst; ///< true if Step is first in Strand
bool const isLast = false; ///< true if Step is last in Strand
@@ -522,11 +531,11 @@ struct StrandContext
at most twice: once as a src and once as a dst (hence the two element
array). The strandSrc and strandDst will only show up once each.
*/
std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues;
std::array<boost::container::flat_set<Asset>, 2>& seenDirectAssets;
/** A strand may not include an offer that output the same issue more
than once
*/
boost::container::flat_set<Issue>& seenBookOuts;
boost::container::flat_set<Asset>& seenBookOuts;
AMMContext& ammContext;
std::optional<uint256> domainID; // the domain the order book will use
beast::Journal const j;
@@ -539,15 +548,15 @@ struct StrandContext
// replicates the source or destination.
AccountID const& strandSrc_,
AccountID const& strandDst_,
Issue const& strandDeliver_,
Asset const& strandDeliver_,
std::optional<Quality> const& limitQuality_,
bool isLast_,
bool ownerPaysTransferFee_,
OfferCrossing offerCrossing_,
bool isDefaultPath_,
std::array<boost::container::flat_set<Issue>, 2>&
seenDirectIssues_, ///< For detecting currency loops
boost::container::flat_set<Issue>& seenBookOuts_, ///< For detecting book loops
std::array<boost::container::flat_set<Asset>, 2>&
seenDirectAssets_, ///< For detecting currency loops
boost::container::flat_set<Asset>& seenBookOuts_, ///< For detecting book loops
AMMContext& ammContext_,
std::optional<uint256> const& domainID,
beast::Journal j_); ///< Journal for logging
@@ -563,6 +572,13 @@ directStepEqual(
AccountID const& dst,
Currency const& currency);
bool
mptEndpointStepEqual(
Step const& step,
AccountID const& src,
AccountID const& dst,
MPTID const& mptid);
bool
xrpEndpointStepEqual(Step const& step, AccountID const& acc);
@@ -577,6 +593,13 @@ make_DirectStepI(
AccountID const& dst,
Currency const& c);
std::pair<TER, std::unique_ptr<Step>>
make_MPTEndpointStep(
StrandContext const& ctx,
AccountID const& src,
AccountID const& dst,
MPTID const& a);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepII(StrandContext const& ctx, Issue const& in, Issue const& out);
@@ -589,9 +612,30 @@ make_BookStepXI(StrandContext const& ctx, Issue const& out);
std::pair<TER, std::unique_ptr<Step>>
make_XRPEndpointStep(StrandContext const& ctx, AccountID const& acc);
template <class InAmt, class OutAmt>
std::pair<TER, std::unique_ptr<Step>>
make_BookStepMM(StrandContext const& ctx, MPTIssue const& in, MPTIssue const& out);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepMX(StrandContext const& ctx, MPTIssue const& in);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepXM(StrandContext const& ctx, MPTIssue const& out);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepMI(StrandContext const& ctx, MPTIssue const& in, Issue const& out);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepIM(StrandContext const& ctx, Issue const& in, MPTIssue const& out);
template <StepAmount InAmt, StepAmount OutAmt>
bool
isDirectXrpToXrp(Strand const& strand);
isDirectXrpToXrp(Strand const& strand)
{
if constexpr (std::is_same_v<InAmt, XRPAmount> && std::is_same_v<OutAmt, XRPAmount>)
return strand.size() == 2;
else
return false;
}
/// @endcond
} // namespace xrpl

View File

@@ -246,7 +246,7 @@ flow(
EitherAmount stepIn(*strand[0]->cachedIn());
for (auto i = 0; i < s; ++i)
{
bool valid;
bool valid = false;
std::tie(valid, stepIn) = strand[i]->validFwd(checkSB, checkAfView, stepIn);
if (!valid)
{
@@ -379,8 +379,10 @@ limitOut(
return XRPAmount{*out};
else if constexpr (std::is_same_v<TOutAmt, IOUAmount>)
return IOUAmount{*out};
else if constexpr (std::is_same_v<TOutAmt, MPTAmount>)
return MPTAmount{*out};
else
return STAmount{remainingOut.issue(), out->mantissa(), out->exponent()};
return STAmount{remainingOut.asset(), out->mantissa(), out->exponent()};
}();
// A tiny difference could be due to the round off
if (withinRelativeDistance(out, remainingOut, Number(1, -9)))
@@ -534,7 +536,7 @@ public:
@return Actual amount in and out from the strands, errors, and payment
sandbox
*/
template <class TInAmt, class TOutAmt>
template <StepAmount TInAmt, StepAmount TOutAmt>
FlowResult<TInAmt, TOutAmt>
flow(
PaymentSandbox const& baseView,

View File

@@ -13,6 +13,9 @@ public:
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -13,6 +13,9 @@ public:
{
}
static bool
checkExtraFeatures(xrpl::PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -13,6 +13,9 @@ public:
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);

View File

@@ -224,7 +224,7 @@ private:
AccountID const& ammAccount,
STAmount const& amount,
STAmount const& amount2,
Issue const& lptIssue,
Asset const& lptIssue,
std::uint16_t tfee);
};

View File

@@ -98,7 +98,8 @@ public:
STAmount const& lpTokens,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee,
FreezeHandling freezeHanding,
FreezeHandling freezeHandling,
AuthHandling authHandling,
WithdrawAll withdrawAll,
XRPAmount const& priorBalance,
beast::Journal const& journal);
@@ -132,6 +133,7 @@ public:
STAmount const& lpTokensWithdraw,
std::uint16_t tfee,
FreezeHandling freezeHandling,
AuthHandling authHandling,
WithdrawAll withdrawAll,
XRPAmount const& priorBalance,
beast::Journal const& journal);
@@ -141,8 +143,8 @@ public:
Sandbox& sb,
std::shared_ptr<SLE> const ammSle,
STAmount const& lpTokenBalance,
Issue const& issue1,
Issue const& issue2,
Asset const& asset1,
Asset const& asset2,
beast::Journal const& journal);
private:

View File

@@ -51,7 +51,7 @@ private:
ApplyFlags const flags,
AccountID const id,
beast::Journal const j,
Issue const& issue);
Asset const& asset);
// Use the payment flow code to perform offer crossing.
std::pair<TER, Amounts>

View File

@@ -33,23 +33,22 @@ public:
TER
doApply() override;
static constexpr auto disabledTxTypes = std::to_array<TxType>({
ttVAULT_CREATE,
ttVAULT_SET,
ttVAULT_DELETE,
ttVAULT_DEPOSIT,
ttVAULT_WITHDRAW,
ttVAULT_CLAWBACK,
ttLOAN_BROKER_SET,
ttLOAN_BROKER_DELETE,
ttLOAN_BROKER_COVER_DEPOSIT,
ttLOAN_BROKER_COVER_WITHDRAW,
ttLOAN_BROKER_COVER_CLAWBACK,
ttLOAN_SET,
ttLOAN_DELETE,
ttLOAN_MANAGE,
ttLOAN_PAY,
});
static constexpr auto disabledTxTypes = std::to_array<TxType>(
{ttVAULT_CREATE,
ttVAULT_SET,
ttVAULT_DELETE,
ttVAULT_DEPOSIT,
ttVAULT_WITHDRAW,
ttVAULT_CLAWBACK,
ttLOAN_BROKER_SET,
ttLOAN_BROKER_DELETE,
ttLOAN_BROKER_COVER_DEPOSIT,
ttLOAN_BROKER_COVER_WITHDRAW,
ttLOAN_BROKER_COVER_CLAWBACK,
ttLOAN_SET,
ttLOAN_DELETE,
ttLOAN_MANAGE,
ttLOAN_PAY});
};
} // namespace xrpl

View File

@@ -0,0 +1,42 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
* @brief Allows an MPT issuer to clawback confidential balances from a holder.
*
* This transaction enables the issuer of an MPToken Issuance (with clawback
* enabled) to reclaim confidential tokens from a holder's account. Unlike
* regular clawback, the issuer cannot see the holder's balance directly.
* Instead, the issuer must provide a zero-knowledge proof that demonstrates
* they know the exact encrypted balance amount.
*
* @par Cryptographic Operations:
* - **Equality Proof Verification**: Verifies that the issuer's revealed
* amount matches the holder's encrypted balance using the issuer's
* ElGamal private key.
*
* @see ConfidentialMPTSend, ConfidentialMPTConvert
*/
class ConfidentialMPTClawback : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ConfidentialMPTClawback(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,44 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
* @brief Converts public (plaintext) MPT balance to confidential (encrypted)
* balance.
*
* This transaction allows a token holder to convert their publicly visible
* MPToken balance into an encrypted confidential balance. Once converted,
* the balance can only be spent using ConfidentialMPTSend transactions and
* remains hidden from public view on the ledger.
*
* @par Cryptographic Operations:
* - **Schnorr Proof Verification**: When registering a new ElGamal public key,
* verifies proof of knowledge of the corresponding private key.
* - **Revealed Amount Verification**: Verifies that the provided encrypted
* amounts (for holder, issuer, and optionally auditor) all encrypt the
* same plaintext amount using the provided blinding factor.
*
* @see ConfidentialMPTConvertBack, ConfidentialMPTSend
*/
class ConfidentialMPTConvert : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ConfidentialMPTConvert(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,45 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
* @brief Converts confidential (encrypted) MPT balance back to public
* (plaintext) balance.
*
* This transaction allows a token holder to convert their encrypted
* confidential balance back into a publicly visible MPToken balance. The
* holder must prove they have sufficient confidential balance without
* revealing the actual balance amount.
*
* @par Cryptographic Operations:
* - **Revealed Amount Verification**: Verifies that the provided encrypted
* amounts correctly encrypt the conversion amount.
* - **Pedersen Linkage Proof**: Verifies that the provided balance commitment
* correctly links to the holder's encrypted spending balance.
* - **Bulletproof Range Proof**: Verifies that the remaining balance (after
* conversion) is non-negative, ensuring the holder has sufficient funds.
*
* @see ConfidentialMPTConvert, ConfidentialMPTSend
*/
class ConfidentialMPTConvertBack : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ConfidentialMPTConvertBack(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,46 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
* @brief Merges the confidential inbox balance into the spending balance.
*
* In the confidential transfer system, incoming funds are deposited into an
* "inbox" balance that the recipient cannot immediately spend. This prevents
* front-running attacks where an attacker could invalidate a pending
* transaction by sending funds to the sender. This transaction merges the
* inbox into the spending balance, making those funds available for spending.
*
* @par Cryptographic Operations:
* - **Homomorphic Addition**: Adds the encrypted inbox balance to the
* encrypted spending balance using ElGamal homomorphic properties.
* - **Zero Encryption**: Resets the inbox to an encryption of zero.
*
* @note This transaction requires no zero-knowledge proofs because it only
* combines encrypted values that the holder already owns. The
* homomorphic properties of ElGamal encryption ensure correctness.
*
* @see ConfidentialMPTSend, ConfidentialMPTConvert
*/
class ConfidentialMPTMergeInbox : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ConfidentialMPTMergeInbox(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,55 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
* @brief Transfers confidential MPT tokens between holders privately.
*
* This transaction enables private token transfers where the transfer amount
* is hidden from public view. Both sender and recipient must have initialized
* confidential balances. The transaction provides encrypted amounts for all
* parties (sender, destination, issuer, and optionally auditor) along with
* zero-knowledge proofs that verify correctness without revealing the amount.
*
* @par Cryptographic Operations:
* - **Multi-Ciphertext Equality Proof**: Verifies that all encrypted amounts
* (sender, destination, issuer, auditor) encrypt the same plaintext value.
* - **Amount Pedersen Linkage Proof**: Verifies that the amount commitment
* correctly links to the sender's encrypted amount.
* - **Balance Pedersen Linkage Proof**: Verifies that the balance commitment
* correctly links to the sender's encrypted spending balance.
* - **Bulletproof Range Proof**: Verifies remaining balance and
* transfer amount are non-negative.
*
* @note Funds are deposited into the destination's inbox, not spending
* balance. The recipient must call ConfidentialMPTMergeInbox to make
* received funds spendable.
*
* @see ConfidentialMPTMergeInbox, ConfidentialMPTConvert,
* ConfidentialMPTConvertBack
*/
class ConfidentialMPTSend : public Transactor
{
/// Size of two Pedersen linkage proofs (amount + balance)
static constexpr std::size_t doublePedersenProofLength = 2 * ecPedersenProofLength;
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ConfidentialMPTSend(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -59,7 +59,8 @@ Logs::File::open(boost::filesystem::path const& path)
bool wasOpened = false;
// VFALCO TODO Make this work with Unicode file paths
std::unique_ptr<std::ofstream> stream(new std::ofstream(path.c_str(), std::fstream::app));
std::unique_ptr<std::ofstream> stream =
std::make_unique<std::ofstream>(path.c_str(), std::fstream::app);
if (stream->good())
{

View File

@@ -43,13 +43,14 @@ AcceptedLedgerTx::AcceptedLedgerTx(
auto const amount = mTxn->getFieldAmount(sfTakerGets);
// If the offer create is not self funded then add the owner balance
if (account != amount.issue().account)
if (account != amount.getIssuer())
{
auto const ownerFunds = accountFunds(
*ledger,
account,
amount,
fhIGNORE_FREEZE,
ahIGNORE_AUTH,
beast::Journal{beast::Journal::getNullSink()});
mJson[jss::transaction][jss::owner_funds] = ownerFunds.getText();
}

View File

@@ -3,12 +3,14 @@
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/SField.h>
#include <algorithm>
namespace xrpl {
namespace detail {
auto
DeferredCredits::makeKey(AccountID const& a1, AccountID const& a2, Currency const& c) -> Key
DeferredCredits::makeKeyIOU(AccountID const& a1, AccountID const& a2, Currency const& c) -> KeyIOU
{
if (a1 < a2)
{
@@ -19,21 +21,23 @@ DeferredCredits::makeKey(AccountID const& a1, AccountID const& a2, Currency cons
}
void
DeferredCredits::credit(
DeferredCredits::creditIOU(
AccountID const& sender,
AccountID const& receiver,
STAmount const& amount,
STAmount const& preCreditSenderBalance)
{
XRPL_ASSERT(
sender != receiver, "xrpl::detail::DeferredCredits::credit : sender is not receiver");
XRPL_ASSERT(!amount.negative(), "xrpl::detail::DeferredCredits::credit : positive amount");
sender != receiver, "xrpl::detail::DeferredCredits::creditIOU : sender is not receiver");
XRPL_ASSERT(!amount.negative(), "xrpl::detail::DeferredCredits::creditIOU : positive amount");
XRPL_ASSERT(
amount.holds<Issue>(), "xrpl::detail::DeferredCredits::creditIOU : amount is for Issue");
auto const k = makeKey(sender, receiver, amount.getCurrency());
auto i = credits_.find(k);
if (i == credits_.end())
auto const k = makeKeyIOU(sender, receiver, amount.get<Issue>().currency);
auto i = creditsIOU_.find(k);
if (i == creditsIOU_.end())
{
Value v;
ValueIOU v;
if (sender < receiver)
{
@@ -48,7 +52,7 @@ DeferredCredits::credit(
v.lowAcctOrigBalance = -preCreditSenderBalance;
}
credits_[k] = v;
creditsIOU_[k] = v;
}
else
{
@@ -65,6 +69,93 @@ DeferredCredits::credit(
}
}
void
DeferredCredits::creditMPT(
AccountID const& sender,
AccountID const& receiver,
STAmount const& amount,
std::uint64_t preCreditBalanceHolder,
std::int64_t preCreditBalanceIssuer)
{
XRPL_ASSERT(
amount.holds<MPTIssue>(),
"xrpl::detail::DeferredCredits::creditMPT : amount is for MPTIssue");
XRPL_ASSERT(!amount.negative(), "xrpl::detail::DeferredCredits::creditMPT : positive amount");
XRPL_ASSERT(
sender != receiver, "xrpl::detail::DeferredCredits::creditMPT : sender is not receiver");
auto const mptAmtVal = amount.mpt().value();
auto const& issuer = amount.getIssuer();
auto const& mptIssue = amount.get<MPTIssue>();
auto const& mptID = mptIssue.getMptID();
bool const isSenderIssuer = sender == issuer;
auto i = creditsMPT_.find(mptID);
if (i == creditsMPT_.end())
{
IssuerValueMPT v;
if (isSenderIssuer)
{
v.credit = mptAmtVal;
v.holders[receiver].origBalance = preCreditBalanceHolder;
}
else
{
v.holders[sender].debit = mptAmtVal;
v.holders[sender].origBalance = preCreditBalanceHolder;
}
v.origBalance = preCreditBalanceIssuer;
creditsMPT_.emplace(mptID, std::move(v));
}
else
{
// only record the balance the first time, do not record it here
auto& v = i->second;
if (isSenderIssuer)
{
v.credit += mptAmtVal;
if (!v.holders.contains(receiver))
{
v.holders[receiver].origBalance = preCreditBalanceHolder;
}
}
else
{
if (!v.holders.contains(sender))
{
v.holders[sender].debit = mptAmtVal;
v.holders[sender].origBalance = preCreditBalanceHolder;
}
else
{
v.holders[sender].debit += mptAmtVal;
}
}
}
}
void
DeferredCredits::issuerSelfDebitMPT(
MPTIssue const& issue,
std::uint64_t amount,
std::int64_t origBalance)
{
auto const& mptID = issue.getMptID();
auto i = creditsMPT_.find(mptID);
if (i == creditsMPT_.end())
{
IssuerValueMPT v;
v.origBalance = origBalance;
v.selfDebit = amount;
creditsMPT_.emplace(mptID, std::move(v));
}
else
{
i->second.selfDebit += amount;
}
}
void
DeferredCredits::ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next)
{
@@ -88,16 +179,16 @@ DeferredCredits::ownerCount(AccountID const& id) const
// Get the adjustments for the balance between main and other.
auto
DeferredCredits::adjustments(
DeferredCredits::adjustmentsIOU(
AccountID const& main,
AccountID const& other,
Currency const& currency) const -> std::optional<Adjustment>
Currency const& currency) const -> std::optional<AdjustmentIOU>
{
std::optional<Adjustment> result;
std::optional<AdjustmentIOU> result;
Key const k = makeKey(main, other, currency);
auto i = credits_.find(k);
if (i == credits_.end())
KeyIOU const k = makeKeyIOU(main, other, currency);
auto i = creditsIOU_.find(k);
if (i == creditsIOU_.end())
return result;
auto const& v = i->second;
@@ -112,12 +203,21 @@ DeferredCredits::adjustments(
return result;
}
auto
DeferredCredits::adjustmentsMPT(xrpl::MPTID const& mptID) const -> std::optional<AdjustmentMPT>
{
auto i = creditsMPT_.find(mptID);
if (i == creditsMPT_.end())
return std::nullopt;
return i->second;
}
void
DeferredCredits::apply(DeferredCredits& to)
{
for (auto const& i : credits_)
for (auto const& i : creditsIOU_)
{
auto r = to.credits_.emplace(i);
auto r = to.creditsIOU_.emplace(i);
if (!r.second)
{
auto& toVal = r.first->second;
@@ -128,6 +228,30 @@ DeferredCredits::apply(DeferredCredits& to)
}
}
for (auto const& i : creditsMPT_)
{
auto r = to.creditsMPT_.emplace(i);
if (!r.second)
{
auto& toVal = r.first->second;
auto const& fromVal = i.second;
toVal.credit += fromVal.credit;
toVal.selfDebit += fromVal.selfDebit;
for (auto& [k, v] : fromVal.holders)
{
if (toVal.holders.find(k) == toVal.holders.end())
{
toVal.holders[k] = v;
}
else
{
toVal.holders[k].debit += v.debit;
}
}
// Do not update the orig balance, it's already correct
}
}
for (auto const& i : ownerCounts_)
{
auto r = to.ownerCounts_.emplace(i);
@@ -143,11 +267,13 @@ DeferredCredits::apply(DeferredCredits& to)
} // namespace detail
STAmount
PaymentSandbox::balanceHook(
PaymentSandbox::balanceHookIOU(
AccountID const& account,
AccountID const& issuer,
STAmount const& amount) const
{
XRPL_ASSERT(amount.holds<Issue>(), "balanceHookIOU: amount is for Issue");
/*
There are two algorithms here. The pre-switchover algorithm takes the
current amount and subtracts the recorded credits. The post-switchover
@@ -159,14 +285,14 @@ PaymentSandbox::balanceHook(
magnitudes, (B+C)-C may not equal B.
*/
auto const currency = amount.getCurrency();
auto const& currency = amount.get<Issue>().currency;
auto delta = amount.zeroed();
auto lastBal = amount;
auto minBal = amount;
for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
{
if (auto adj = curSB->tab_.adjustments(account, issuer, currency))
if (auto adj = curSB->tab_.adjustmentsIOU(account, issuer, currency))
{
delta += adj->debits;
lastBal = adj->origBalance;
@@ -180,13 +306,13 @@ PaymentSandbox::balanceHook(
// to compute usable balance just slightly above what the ledger
// calculates (but always less than the actual balance).
auto adjustedAmt = std::min({amount, lastBal - delta, minBal});
adjustedAmt.setIssuer(amount.getIssuer());
adjustedAmt.get<Issue>().account = amount.getIssuer();
if (isXRP(issuer) && adjustedAmt < beast::zero)
{
// A calculated negative XRP balance is not an error case. Consider a
// payment snippet that credits a large XRP amount and then debits the
// same amount. The credit can't be used but we subtract the debit and
// same amount. The credit can't be used, but we subtract the debit and
// calculate a negative value. It's not an error case.
adjustedAmt.clear();
}
@@ -194,6 +320,64 @@ PaymentSandbox::balanceHook(
return adjustedAmt;
}
STAmount
PaymentSandbox::balanceHookMPT(AccountID const& account, MPTIssue const& issue, std::int64_t amount)
const
{
auto const& issuer = issue.getIssuer();
bool const accountIsHolder = account != issuer;
std::int64_t delta = 0;
std::int64_t lastBal = amount;
std::int64_t minBal = amount;
for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
{
if (auto adj = curSB->tab_.adjustmentsMPT(issue))
{
if (accountIsHolder)
{
if (auto const i = adj->holders.find(account); i != adj->holders.end())
{
delta += i->second.debit;
lastBal = i->second.origBalance;
}
}
else
{
delta += adj->credit;
lastBal = adj->origBalance;
}
minBal = std::min(lastBal, minBal);
}
}
// The adjusted amount should never be larger than the balance.
auto const adjustedAmt = std::min({amount, lastBal - delta, minBal});
return adjustedAmt > 0 ? STAmount{issue, adjustedAmt} : STAmount{issue};
}
STAmount
PaymentSandbox::balanceHookSelfIssueMPT(xrpl::MPTIssue const& issue, std::int64_t amount) const
{
std::int64_t selfDebited = 0;
std::int64_t lastBal = amount;
for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
{
if (auto adj = curSB->tab_.adjustmentsMPT(issue))
{
selfDebited += adj->selfDebit;
lastBal = adj->origBalance;
}
}
if (lastBal > selfDebited)
return STAmount{issue, lastBal - selfDebited};
return STAmount{issue};
}
std::uint32_t
PaymentSandbox::ownerCountHook(AccountID const& account, std::uint32_t count) const
{
@@ -207,13 +391,39 @@ PaymentSandbox::ownerCountHook(AccountID const& account, std::uint32_t count) co
}
void
PaymentSandbox::creditHook(
PaymentSandbox::creditHookIOU(
AccountID const& from,
AccountID const& to,
STAmount const& amount,
STAmount const& preCreditBalance)
{
tab_.credit(from, to, amount, preCreditBalance);
XRPL_ASSERT(amount.holds<Issue>(), "creditHookIOU: amount is for Issue");
tab_.creditIOU(from, to, amount, preCreditBalance);
}
void
PaymentSandbox::creditHookMPT(
AccountID const& from,
AccountID const& to,
STAmount const& amount,
std::uint64_t preCreditBalanceHolder,
std::int64_t preCreditBalanceIssuer)
{
XRPL_ASSERT(amount.holds<MPTIssue>(), "creditHookMPT: amount is for MPTIssue");
tab_.creditMPT(from, to, amount, preCreditBalanceHolder, preCreditBalanceIssuer);
}
void
PaymentSandbox::issuerSelfDebitHookMPT(
MPTIssue const& issue,
std::uint64_t amount,
std::int64_t origBalance)
{
XRPL_ASSERT(amount > 0, "PaymentSandbox::issuerSelfDebitHookMPT: amount must be > 0");
tab_.issuerSelfDebitMPT(issue, amount, origBalance);
}
void
@@ -346,7 +556,7 @@ PaymentSandbox::balanceChanges(ReadView const& view) const
}
// The following are now set, put them in the map
auto delta = newBalance - oldBalance;
auto const cur = newBalance.getCurrency();
auto const cur = newBalance.get<Issue>().currency;
result[std::make_tuple(lowID, highID, cur)] = delta;
auto r = result.emplace(std::make_tuple(lowID, lowID, cur), delta);
if (r.second)

View File

@@ -84,11 +84,10 @@ bool
isLPTokenFrozen(
ReadView const& view,
AccountID const& account,
Issue const& asset,
Issue const& asset2)
Asset const& asset,
Asset const& asset2)
{
return isFrozen(view, account, asset.currency, asset.account) ||
isFrozen(view, account, asset2.currency, asset2.account);
return isFrozen(view, account, asset) || isFrozen(view, account, asset2);
}
bool
@@ -334,22 +333,19 @@ withdrawToDestExceedsLimit(
if (from == to || to == issuer || isXRP(issuer))
return tesSUCCESS;
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
if constexpr (std::is_same_v<TIss, Issue>)
return amount.asset().visit(
[&](Issue const& issue) -> TER {
auto const& currency = issue.currency;
auto const owed = creditBalance(view, to, issuer, currency);
if (owed <= beast::zero)
{
auto const& currency = issue.currency;
auto const owed = creditBalance(view, to, issuer, currency);
if (owed <= beast::zero)
{
auto const limit = creditLimit(view, to, issuer, currency);
if (-owed >= limit || amount > (limit + owed))
return tecNO_LINE;
}
auto const limit = creditLimit(view, to, issuer, currency);
if (-owed >= limit || amount > (limit + owed))
return tecNO_LINE;
}
return tesSUCCESS;
},
amount.asset().value());
[](MPTIssue const&) -> TER { return tesSUCCESS; });
}
[[nodiscard]] TER

View File

@@ -1,9 +1,11 @@
#include <xrpl/ledger/helpers/AMMHelpers.h>
//
#include <xrpl/ledger/View.h>
namespace xrpl {
STAmount
ammLPTokens(STAmount const& asset1, STAmount const& asset2, Issue const& lptIssue)
ammLPTokens(STAmount const& asset1, STAmount const& asset2, Asset const& lptIssue)
{
// AMM invariant: sqrt(asset1 * asset2) >= LPTokensBalance
auto const rounding = isFeatureEnabled(fixAMMv1_3) ? Number::downward : Number::getround();
@@ -32,7 +34,7 @@ lpTokensOut(
if (!isFeatureEnabled(fixAMMv1_3))
{
auto const t = lptAMMBalance * (r - c) / (1 + c);
return toSTAmount(lptAMMBalance.issue(), t);
return toSTAmount(lptAMMBalance.asset(), t);
}
// minimize tokens out
@@ -68,7 +70,7 @@ ammAssetIn(
auto const c = d * d - f2 * f2;
if (!isFeatureEnabled(fixAMMv1_3))
{
return toSTAmount(asset1Balance.issue(), asset1Balance * solveQuadraticEq(a, b, c));
return toSTAmount(asset1Balance.asset(), asset1Balance * solveQuadraticEq(a, b, c));
}
// maximize deposit
@@ -93,7 +95,7 @@ lpTokensIn(
if (!isFeatureEnabled(fixAMMv1_3))
{
auto const t = lptAMMBalance * (c - root2(c * c - 4 * fr)) / 2;
return toSTAmount(lptAMMBalance.issue(), t);
return toSTAmount(lptAMMBalance.asset(), t);
}
// maximize tokens in
@@ -123,7 +125,7 @@ ammAssetOut(
if (!isFeatureEnabled(fixAMMv1_3))
{
auto const b = assetBalance * (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1);
return toSTAmount(assetBalance.issue(), b);
return toSTAmount(assetBalance.asset(), b);
}
// minimize withdraw
@@ -183,8 +185,8 @@ adjustAmountsByLPTokens(
if (amount2)
{
Number const fr = lpTokensActual / lpTokens;
auto const amountActual = toSTAmount(amount.issue(), fr * amount);
auto const amount2Actual = toSTAmount(amount2->issue(), fr * *amount2);
auto const amountActual = toSTAmount(amount.asset(), fr * amount);
auto const amount2Actual = toSTAmount(amount2->asset(), fr * *amount2);
if (!ammRoundingEnabled)
{
return std::make_tuple(
@@ -253,7 +255,7 @@ multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm)
{
NumberRoundModeGuard const g(rm);
auto const t = amount * frac;
return toSTAmount(amount.issue(), t, rm);
return toSTAmount(amount.asset(), t, rm);
}
STAmount
@@ -265,13 +267,13 @@ getRoundedAsset(
IsDeposit isDeposit)
{
if (!rules.enabled(fixAMMv1_3))
return toSTAmount(balance.issue(), noRoundCb());
return toSTAmount(balance.asset(), noRoundCb());
auto const rm = detail::getAssetRounding(isDeposit);
if (isDeposit == IsDeposit::Yes)
return multiply(balance, productCb(), rm);
NumberRoundModeGuard const g(rm);
return toSTAmount(balance.issue(), productCb(), rm);
return toSTAmount(balance.asset(), productCb(), rm);
}
STAmount
@@ -282,7 +284,7 @@ getRoundedLPTokens(
IsDeposit isDeposit)
{
if (!rules.enabled(fixAMMv1_3))
return toSTAmount(balance.issue(), balance * frac);
return toSTAmount(balance.asset(), balance * frac);
auto const rm = detail::getLPTokenRounding(isDeposit);
auto const tokens = multiply(balance, frac, rm);
@@ -298,14 +300,14 @@ getRoundedLPTokens(
IsDeposit isDeposit)
{
if (!rules.enabled(fixAMMv1_3))
return toSTAmount(lptAMMBalance.issue(), noRoundCb());
return toSTAmount(lptAMMBalance.asset(), noRoundCb());
auto const tokens = [&] {
auto const rm = detail::getLPTokenRounding(isDeposit);
if (isDeposit == IsDeposit::Yes)
{
NumberRoundModeGuard const g(rm);
return toSTAmount(lptAMMBalance.issue(), productCb(), rm);
return toSTAmount(lptAMMBalance.asset(), productCb(), rm);
}
return multiply(lptAMMBalance, productCb(), rm);
}();
@@ -376,4 +378,535 @@ adjustFracByTokens(
return tokens / lptAMMBalance;
}
std::pair<STAmount, STAmount>
ammPoolHolds(
ReadView const& view,
AccountID const& ammAccountID,
Asset const& asset1,
Asset const& asset2,
FreezeHandling freezeHandling,
AuthHandling authHandling,
beast::Journal const j)
{
auto const assetInBalance =
accountHolds(view, ammAccountID, asset1, freezeHandling, authHandling, j);
auto const assetOutBalance =
accountHolds(view, ammAccountID, asset2, freezeHandling, authHandling, j);
return std::make_pair(assetInBalance, assetOutBalance);
}
Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
ammHolds(
ReadView const& view,
SLE const& ammSle,
std::optional<Asset> const& optAsset1,
std::optional<Asset> const& optAsset2,
FreezeHandling freezeHandling,
AuthHandling authHandling,
beast::Journal const j)
{
auto const assets = [&]() -> std::optional<std::pair<Asset, Asset>> {
auto const asset1 = ammSle[sfAsset];
auto const asset2 = ammSle[sfAsset2];
if (optAsset1 && optAsset2)
{
if (invalidAMMAssetPair(
*optAsset1, *optAsset2, std::make_optional(std::make_pair(asset1, asset2))))
{
// This error can only be hit if the AMM is corrupted
// LCOV_EXCL_START
JLOG(j.debug()) << "ammHolds: Invalid optAsset1 or optAsset2 " << *optAsset1 << " "
<< *optAsset2;
return std::nullopt;
// LCOV_EXCL_STOP
}
return std::make_optional(std::make_pair(*optAsset1, *optAsset2));
}
auto const singleAsset = [&asset1, &asset2, &j](
Asset checkIssue,
char const* label) -> std::optional<std::pair<Asset, Asset>> {
if (checkIssue == asset1)
{
return std::make_optional(std::make_pair(asset1, asset2));
}
if (checkIssue == asset2)
{
return std::make_optional(std::make_pair(asset2, asset1));
}
// Unreachable unless AMM corrupted.
// LCOV_EXCL_START
JLOG(j.debug()) << "ammHolds: Invalid " << label << " " << checkIssue;
return std::nullopt;
// LCOV_EXCL_STOP
};
if (optAsset1)
{
return singleAsset(*optAsset1, "optAsset1");
}
if (optAsset2)
{
// Cannot have Amount2 without Amount.
return singleAsset(*optAsset2, "optAsset2"); // LCOV_EXCL_LINE
}
return std::make_optional(std::make_pair(asset1, asset2));
}();
if (!assets)
return Unexpected(tecAMM_INVALID_TOKENS);
auto const [amount1, amount2] = ammPoolHolds(
view,
ammSle.getAccountID(sfAccount),
assets->first,
assets->second,
freezeHandling,
authHandling,
j);
return std::make_tuple(amount1, amount2, ammSle[sfLPTokenBalance]);
}
STAmount
ammLPHolds(
ReadView const& view,
Asset const& asset1,
Asset const& asset2,
AccountID const& ammAccount,
AccountID const& lpAccount,
beast::Journal const j)
{
// This function looks similar to `accountHolds`. However, it only checks if
// a LPToken holder has enough balance. On the other hand, `accountHolds`
// checks if the underlying assets of LPToken are frozen with the
// fixFrozenLPTokenTransfer amendment
auto const currency = ammLPTCurrency(asset1, asset2);
STAmount amount;
auto const sle = view.read(keylet::line(lpAccount, ammAccount, currency));
if (!sle)
{
amount.clear(Issue{currency, ammAccount});
JLOG(j.trace()) << "ammLPHolds: no SLE "
<< " lpAccount=" << to_string(lpAccount)
<< " amount=" << amount.getFullText();
}
else if (isFrozen(view, lpAccount, currency, ammAccount))
{
amount.clear(Issue{currency, ammAccount});
JLOG(j.trace()) << "ammLPHolds: frozen currency "
<< " lpAccount=" << to_string(lpAccount)
<< " amount=" << amount.getFullText();
}
else
{
amount = sle->getFieldAmount(sfBalance);
if (lpAccount > ammAccount)
{
// Put balance in account terms.
amount.negate();
}
amount.get<Issue>().account = ammAccount;
JLOG(j.trace()) << "ammLPHolds:"
<< " lpAccount=" << to_string(lpAccount)
<< " amount=" << amount.getFullText();
}
return view.balanceHookIOU(lpAccount, ammAccount, amount);
}
STAmount
ammLPHolds(
ReadView const& view,
SLE const& ammSle,
AccountID const& lpAccount,
beast::Journal const j)
{
return ammLPHolds(view, ammSle[sfAsset], ammSle[sfAsset2], ammSle[sfAccount], lpAccount, j);
}
std::uint16_t
getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account)
{
using namespace std::chrono;
XRPL_ASSERT(
!view.rules().enabled(fixInnerObjTemplate) || ammSle.isFieldPresent(sfAuctionSlot),
"xrpl::getTradingFee : auction present");
if (ammSle.isFieldPresent(sfAuctionSlot))
{
auto const& auctionSlot = safe_downcast<STObject const&>(ammSle.peekAtField(sfAuctionSlot));
// Not expired
if (auto const expiration = auctionSlot[~sfExpiration];
duration_cast<seconds>(view.header().parentCloseTime.time_since_epoch()).count() <
expiration)
{
if (auctionSlot[~sfAccount] == account)
return auctionSlot[sfDiscountedFee];
if (auctionSlot.isFieldPresent(sfAuthAccounts))
{
for (auto const& acct : auctionSlot.getFieldArray(sfAuthAccounts))
{
if (acct[~sfAccount] == account)
return auctionSlot[sfDiscountedFee];
}
}
}
}
return ammSle[sfTradingFee];
}
STAmount
ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const& asset)
{
// Get the actual AMM balance without factoring in the balance hook
return asset.visit(
[&](MPTIssue const& issue) {
if (auto const sle = view.read(keylet::mptoken(issue, ammAccountID));
sle && !isFrozen(view, ammAccountID, issue))
return STAmount{issue, (*sle)[sfMPTAmount]};
return STAmount{asset};
},
[&](Issue const& issue) {
if (isXRP(issue))
{
if (auto const sle = view.read(keylet::account(ammAccountID)))
return (*sle)[sfBalance];
}
else if (
auto const sle =
view.read(keylet::line(ammAccountID, issue.account, issue.currency));
sle && !isFrozen(view, ammAccountID, issue.currency, issue.account))
{
STAmount amount = (*sle)[sfBalance];
if (ammAccountID > issue.account)
amount.negate();
amount.get<Issue>().account = issue.account;
return amount;
}
return STAmount{asset};
});
}
static TER
deleteAMMTrustLines(
Sandbox& sb,
AccountID const& ammAccountID,
std::uint16_t maxTrustlinesToDelete,
beast::Journal j)
{
return cleanupOnAccountDelete(
sb,
keylet::ownerDir(ammAccountID),
[&](LedgerEntryType nodeType,
uint256 const&,
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
// Skip AMM and MPToken
if (nodeType == ltAMM || nodeType == ltMPTOKEN)
return {tesSUCCESS, SkipEntry::Yes};
if (nodeType == ltRIPPLE_STATE)
{
// Trustlines must have zero balance
if (sleItem->getFieldAmount(sfBalance) != beast::zero)
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMObjects: deleting trustline with "
"non-zero balance.";
return {tecINTERNAL, SkipEntry::No};
// LCOV_EXCL_STOP
}
return {deleteAMMTrustLine(sb, sleItem, ammAccountID, j), SkipEntry::No};
}
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMObjects: deleting non-trustline or non-MPT " << nodeType;
return {tecINTERNAL, SkipEntry::No};
// LCOV_EXCL_STOP
},
j,
maxTrustlinesToDelete);
}
static TER
deleteAMMMPTokens(Sandbox& sb, AccountID const& ammAccountID, beast::Journal j)
{
return cleanupOnAccountDelete(
sb,
keylet::ownerDir(ammAccountID),
[&](LedgerEntryType nodeType,
uint256 const&,
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
// Skip AMM
if (nodeType == ltAMM)
return {tesSUCCESS, SkipEntry::Yes};
if (nodeType == ltMPTOKEN)
{
// MPT must have zero balance
if (sleItem->getFieldU64(sfMPTAmount) != 0 ||
(*sleItem)[~sfLockedAmount].value_or(0) != 0)
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMObjects: deleting MPT with "
"non-zero balance.";
return {tecINTERNAL, SkipEntry::No};
// LCOV_EXCL_STOP
}
return {deleteAMMMPToken(sb, sleItem, ammAccountID, j), SkipEntry::No};
}
if (nodeType == ltRIPPLE_STATE)
{
// Trustlines should have been deleted
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMObjects: trustlines should have been deleted";
return {tecINTERNAL, SkipEntry::No};
// LCOV_EXCL_STOP
}
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMObjects: deleting non-trustline or non-MPT " << nodeType;
return {tecINTERNAL, SkipEntry::No};
// LCOV_EXCL_STOP
},
j,
3); // At most two MPToken plus AMM object
}
TER
deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Journal j)
{
auto ammSle = sb.peek(keylet::amm(asset, asset2));
if (!ammSle)
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMAccount: AMM object does not exist " << asset << " " << asset2;
return tecINTERNAL;
// LCOV_EXCL_STOP
}
auto const ammAccountID = (*ammSle)[sfAccount];
auto sleAMMRoot = sb.peek(keylet::account(ammAccountID));
if (!sleAMMRoot)
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMAccount: AMM account does not exist "
<< to_string(ammAccountID);
return tecINTERNAL;
// LCOV_EXCL_STOP
}
if (auto const ter = deleteAMMTrustLines(sb, ammAccountID, maxDeletableAMMTrustLines, j);
!isTesSuccess(ter))
return ter;
// Delete AMM's MPTokens only if all trustlines are deleted. If trustlines
// are not deleted then AMM can be re-created with Deposit and
// AMM's MPToken(s) must exist.
if (auto const ter = deleteAMMMPTokens(sb, ammAccountID, j); !isTesSuccess(ter))
return ter;
auto const ownerDirKeylet = keylet::ownerDir(ammAccountID);
if (!sb.dirRemove(ownerDirKeylet, (*ammSle)[sfOwnerNode], ammSle->key(), false))
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMAccount: failed to remove dir link";
return tecINTERNAL;
// LCOV_EXCL_STOP
}
if (sb.exists(ownerDirKeylet) && !sb.emptyDirDelete(ownerDirKeylet))
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMAccount: cannot delete root dir node of "
<< toBase58(ammAccountID);
return tecINTERNAL;
// LCOV_EXCL_STOP
}
sb.erase(ammSle);
sb.erase(sleAMMRoot);
return tesSUCCESS;
}
void
initializeFeeAuctionVote(
ApplyView& view,
std::shared_ptr<SLE>& ammSle,
AccountID const& account,
Asset const& lptAsset,
std::uint16_t tfee)
{
auto const& rules = view.rules();
// AMM creator gets the voting slot.
STArray voteSlots;
STObject voteEntry = STObject::makeInnerObject(sfVoteEntry);
if (tfee != 0)
voteEntry.setFieldU16(sfTradingFee, tfee);
voteEntry.setFieldU32(sfVoteWeight, VOTE_WEIGHT_SCALE_FACTOR);
voteEntry.setAccountID(sfAccount, account);
voteSlots.push_back(voteEntry);
ammSle->setFieldArray(sfVoteSlots, voteSlots);
// AMM creator gets the auction slot for free.
// AuctionSlot is created on AMMCreate and updated on AMMDeposit
// when AMM is in an empty state
if (rules.enabled(fixInnerObjTemplate) && !ammSle->isFieldPresent(sfAuctionSlot))
{
STObject auctionSlot = STObject::makeInnerObject(sfAuctionSlot);
ammSle->set(std::move(auctionSlot));
}
STObject& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
auctionSlot.setAccountID(sfAccount, account);
// current + sec in 24h
auto const expiration = std::chrono::duration_cast<std::chrono::seconds>(
view.header().parentCloseTime.time_since_epoch())
.count() +
TOTAL_TIME_SLOT_SECS;
auctionSlot.setFieldU32(sfExpiration, expiration);
auctionSlot.setFieldAmount(sfPrice, STAmount{lptAsset, 0});
// Set the fee
if (tfee != 0)
{
ammSle->setFieldU16(sfTradingFee, tfee);
}
else if (ammSle->isFieldPresent(sfTradingFee))
{
ammSle->makeFieldAbsent(sfTradingFee); // LCOV_EXCL_LINE
}
if (auto const dfee = tfee / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION)
{
auctionSlot.setFieldU16(sfDiscountedFee, dfee);
}
else if (auctionSlot.isFieldPresent(sfDiscountedFee))
{
auctionSlot.makeFieldAbsent(sfDiscountedFee); // LCOV_EXCL_LINE
}
}
Expected<bool, TER>
isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount)
{
// Liquidity Provider (LP) must have one LPToken trustline
std::uint8_t nLPTokenTrustLines = 0;
// AMM account has at most two IOU (pool tokens, not LPToken) trustlines.
// One or both trustlines could be to the LP if LP is the issuer,
// or a different account if LP is not an issuer. For instance,
// if AMM has two tokens USD and EUR and LP is not the issuer of the tokens
// then the trustlines are between AMM account and the issuer.
// There is one LPToken trustline for each LP. Only remaining LP has
// exactly one LPToken trustlines and at most two IOU trustline for each
// pool token. One or both tokens could be MPT.
std::uint8_t nIOUTrustLines = 0;
// There are at most two MPT objects, one for each side of the pool.
std::uint8_t nMPT = 0;
// There is only one AMM object
bool hasAMM = false;
// AMM LP has at most three trustlines, at most two MPTs, and only one
// AMM object must exist. If there are more than four objects then
// it's either an error or there are more than one LP. Ten pages should
// be sufficient to include four objects.
std::uint8_t limit = 10;
auto const root = keylet::ownerDir(ammIssue.account);
auto currentIndex = root;
// Iterate over AMM owner directory objects.
while (limit-- >= 1)
{
auto const ownerDir = view.read(currentIndex);
if (!ownerDir)
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
for (auto const& key : ownerDir->getFieldV256(sfIndexes))
{
auto const sle = view.read(keylet::child(key));
if (!sle)
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
auto const entryType = sle->getFieldU16(sfLedgerEntryType);
// Only one AMM object
if (entryType == ltAMM)
{
if (hasAMM)
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
hasAMM = true;
continue;
}
if (entryType == ltMPTOKEN)
{
++nMPT;
continue;
}
if (entryType != ltRIPPLE_STATE)
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
auto const lowLimit = sle->getFieldAmount(sfLowLimit);
auto const highLimit = sle->getFieldAmount(sfHighLimit);
auto const isLPTrustline =
lowLimit.getIssuer() == lpAccount || highLimit.getIssuer() == lpAccount;
auto const isLPTokenTrustline =
lowLimit.asset() == ammIssue || highLimit.asset() == ammIssue;
// Liquidity Provider trustline
if (isLPTrustline)
{
// LPToken trustline
if (isLPTokenTrustline)
{
// LP has exactly one LPToken trustline
if (++nLPTokenTrustLines > 1)
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
}
// AMM account has at most two IOU trustlines
else if (++nIOUTrustLines > 2)
{
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
}
}
// Another Liquidity Provider LPToken trustline
else if (isLPTokenTrustline)
{
return false;
}
// AMM account has at most two IOU trustlines
else if (++nIOUTrustLines > 2)
{
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
}
}
auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
if (uNodeNext == 0)
{
if (nLPTokenTrustLines != 1 || (nIOUTrustLines == 0 && nMPT == 0) ||
(nIOUTrustLines > 2 || nMPT > 2) || (nIOUTrustLines + nMPT) > 2)
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
return true;
}
currentIndex = keylet::page(root, uNodeNext);
}
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
}
Expected<bool, TER>
verifyAndAdjustLPTokenBalance(
Sandbox& sb,
STAmount const& lpTokens,
std::shared_ptr<SLE>& ammSle,
AccountID const& account)
{
auto const res = isOnlyLiquidityProvider(sb, lpTokens.get<Issue>(), account);
if (!res.has_value())
{
return Unexpected<TER>(res.error());
}
if (res.value())
{
if (withinRelativeDistance(
lpTokens, ammSle->getFieldAmount(sfLPTokenBalance), Number{1, -3}))
{
ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
sb.update(ammSle);
}
else
{
return Unexpected<TER>(tecAMM_INVALID_TOKENS);
}
}
return true;
}
} // namespace xrpl

View File

@@ -1,462 +0,0 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/AMMUtils.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/STObject.h>
namespace xrpl {
std::pair<STAmount, STAmount>
ammPoolHolds(
ReadView const& view,
AccountID const& ammAccountID,
Issue const& issue1,
Issue const& issue2,
FreezeHandling freezeHandling,
beast::Journal const j)
{
auto const assetInBalance = accountHolds(view, ammAccountID, issue1, freezeHandling, j);
auto const assetOutBalance = accountHolds(view, ammAccountID, issue2, freezeHandling, j);
return std::make_pair(assetInBalance, assetOutBalance);
}
Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
ammHolds(
ReadView const& view,
SLE const& ammSle,
std::optional<Issue> const& optIssue1,
std::optional<Issue> const& optIssue2,
FreezeHandling freezeHandling,
beast::Journal const j)
{
auto const issues = [&]() -> std::optional<std::pair<Issue, Issue>> {
auto const issue1 = ammSle[sfAsset].get<Issue>();
auto const issue2 = ammSle[sfAsset2].get<Issue>();
if (optIssue1 && optIssue2)
{
if (invalidAMMAssetPair(
*optIssue1, *optIssue2, std::make_optional(std::make_pair(issue1, issue2))))
{
// This error can only be hit if the AMM is corrupted
// LCOV_EXCL_START
JLOG(j.debug()) << "ammHolds: Invalid optIssue1 or optIssue2 " << *optIssue1 << " "
<< *optIssue2;
return std::nullopt;
// LCOV_EXCL_STOP
}
return std::make_optional(std::make_pair(*optIssue1, *optIssue2));
}
auto const singleIssue = [&issue1, &issue2, &j](
Issue checkIssue,
char const* label) -> std::optional<std::pair<Issue, Issue>> {
if (checkIssue == issue1)
{
return std::make_optional(std::make_pair(issue1, issue2));
}
if (checkIssue == issue2)
{
return std::make_optional(std::make_pair(issue2, issue1));
}
// Unreachable unless AMM corrupted.
// LCOV_EXCL_START
JLOG(j.debug()) << "ammHolds: Invalid " << label << " " << checkIssue;
return std::nullopt;
// LCOV_EXCL_STOP
};
if (optIssue1)
{
return singleIssue(*optIssue1, "optIssue1");
}
if (optIssue2)
{
// Cannot have Amount2 without Amount.
return singleIssue(*optIssue2, "optIssue2"); // LCOV_EXCL_LINE
}
return std::make_optional(std::make_pair(issue1, issue2));
}();
if (!issues)
return Unexpected(tecAMM_INVALID_TOKENS);
auto const [asset1, asset2] = ammPoolHolds(
view, ammSle.getAccountID(sfAccount), issues->first, issues->second, freezeHandling, j);
return std::make_tuple(asset1, asset2, ammSle[sfLPTokenBalance]);
}
STAmount
ammLPHolds(
ReadView const& view,
Currency const& cur1,
Currency const& cur2,
AccountID const& ammAccount,
AccountID const& lpAccount,
beast::Journal const j)
{
// This function looks similar to `accountHolds`. However, it only checks if
// a LPToken holder has enough balance. On the other hand, `accountHolds`
// checks if the underlying assets of LPToken are frozen with the
// fixFrozenLPTokenTransfer amendment
auto const currency = ammLPTCurrency(cur1, cur2);
STAmount amount;
auto const sle = view.read(keylet::line(lpAccount, ammAccount, currency));
if (!sle)
{
amount.clear(Issue{currency, ammAccount});
JLOG(j.trace()) << "ammLPHolds: no SLE "
<< " lpAccount=" << to_string(lpAccount)
<< " amount=" << amount.getFullText();
}
else if (isFrozen(view, lpAccount, currency, ammAccount))
{
amount.clear(Issue{currency, ammAccount});
JLOG(j.trace()) << "ammLPHolds: frozen currency "
<< " lpAccount=" << to_string(lpAccount)
<< " amount=" << amount.getFullText();
}
else
{
amount = sle->getFieldAmount(sfBalance);
if (lpAccount > ammAccount)
{
// Put balance in account terms.
amount.negate();
}
amount.setIssuer(ammAccount);
JLOG(j.trace()) << "ammLPHolds:"
<< " lpAccount=" << to_string(lpAccount)
<< " amount=" << amount.getFullText();
}
return view.balanceHook(lpAccount, ammAccount, amount);
}
STAmount
ammLPHolds(
ReadView const& view,
SLE const& ammSle,
AccountID const& lpAccount,
beast::Journal const j)
{
return ammLPHolds(
view,
ammSle[sfAsset].get<Issue>().currency,
ammSle[sfAsset2].get<Issue>().currency,
ammSle[sfAccount],
lpAccount,
j);
}
std::uint16_t
getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account)
{
using namespace std::chrono;
XRPL_ASSERT(
!view.rules().enabled(fixInnerObjTemplate) || ammSle.isFieldPresent(sfAuctionSlot),
"xrpl::getTradingFee : auction present");
if (ammSle.isFieldPresent(sfAuctionSlot))
{
auto const& auctionSlot = safe_downcast<STObject const&>(ammSle.peekAtField(sfAuctionSlot));
// Not expired
if (auto const expiration = auctionSlot[~sfExpiration];
duration_cast<seconds>(view.header().parentCloseTime.time_since_epoch()).count() <
expiration)
{
if (auctionSlot[~sfAccount] == account)
return auctionSlot[sfDiscountedFee];
if (auctionSlot.isFieldPresent(sfAuthAccounts))
{
for (auto const& acct : auctionSlot.getFieldArray(sfAuthAccounts))
{
if (acct[~sfAccount] == account)
return auctionSlot[sfDiscountedFee];
}
}
}
}
return ammSle[sfTradingFee];
}
STAmount
ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Issue const& issue)
{
if (isXRP(issue))
{
if (auto const sle = view.read(keylet::account(ammAccountID)))
return (*sle)[sfBalance];
}
else if (
auto const sle = view.read(keylet::line(ammAccountID, issue.account, issue.currency));
sle && !isFrozen(view, ammAccountID, issue.currency, issue.account))
{
auto amount = (*sle)[sfBalance];
if (ammAccountID > issue.account)
amount.negate();
amount.setIssuer(issue.account);
return amount;
}
return STAmount{issue};
}
static TER
deleteAMMTrustLines(
Sandbox& sb,
AccountID const& ammAccountID,
std::uint16_t maxTrustlinesToDelete,
beast::Journal j)
{
return cleanupOnAccountDelete(
sb,
keylet::ownerDir(ammAccountID),
[&](LedgerEntryType nodeType,
uint256 const&,
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
// Skip AMM
if (nodeType == LedgerEntryType::ltAMM)
return {tesSUCCESS, SkipEntry::Yes};
// Should only have the trustlines
if (nodeType != LedgerEntryType::ltRIPPLE_STATE)
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMTrustLines: deleting non-trustline " << nodeType;
return {tecINTERNAL, SkipEntry::No};
// LCOV_EXCL_STOP
}
// Trustlines must have zero balance
if (sleItem->getFieldAmount(sfBalance) != beast::zero)
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMTrustLines: deleting trustline with "
"non-zero balance.";
return {tecINTERNAL, SkipEntry::No};
// LCOV_EXCL_STOP
}
return {deleteAMMTrustLine(sb, sleItem, ammAccountID, j), SkipEntry::No};
},
j,
maxTrustlinesToDelete);
}
TER
deleteAMMAccount(Sandbox& sb, Issue const& asset, Issue const& asset2, beast::Journal j)
{
auto ammSle = sb.peek(keylet::amm(asset, asset2));
if (!ammSle)
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMAccount: AMM object does not exist " << asset << " " << asset2;
return tecINTERNAL;
// LCOV_EXCL_STOP
}
auto const ammAccountID = (*ammSle)[sfAccount];
auto sleAMMRoot = sb.peek(keylet::account(ammAccountID));
if (!sleAMMRoot)
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMAccount: AMM account does not exist "
<< to_string(ammAccountID);
return tecINTERNAL;
// LCOV_EXCL_STOP
}
if (auto const ter = deleteAMMTrustLines(sb, ammAccountID, maxDeletableAMMTrustLines, j);
!isTesSuccess(ter))
return ter;
auto const ownerDirKeylet = keylet::ownerDir(ammAccountID);
if (!sb.dirRemove(ownerDirKeylet, (*ammSle)[sfOwnerNode], ammSle->key(), false))
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMAccount: failed to remove dir link";
return tecINTERNAL;
// LCOV_EXCL_STOP
}
if (sb.exists(ownerDirKeylet) && !sb.emptyDirDelete(ownerDirKeylet))
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMAccount: cannot delete root dir node of "
<< toBase58(ammAccountID);
return tecINTERNAL;
// LCOV_EXCL_STOP
}
sb.erase(ammSle);
sb.erase(sleAMMRoot);
return tesSUCCESS;
}
void
initializeFeeAuctionVote(
ApplyView& view,
std::shared_ptr<SLE>& ammSle,
AccountID const& account,
Issue const& lptIssue,
std::uint16_t tfee)
{
auto const& rules = view.rules();
// AMM creator gets the voting slot.
STArray voteSlots;
STObject voteEntry = STObject::makeInnerObject(sfVoteEntry);
if (tfee != 0)
voteEntry.setFieldU16(sfTradingFee, tfee);
voteEntry.setFieldU32(sfVoteWeight, VOTE_WEIGHT_SCALE_FACTOR);
voteEntry.setAccountID(sfAccount, account);
voteSlots.push_back(voteEntry);
ammSle->setFieldArray(sfVoteSlots, voteSlots);
// AMM creator gets the auction slot for free.
// AuctionSlot is created on AMMCreate and updated on AMMDeposit
// when AMM is in an empty state
if (rules.enabled(fixInnerObjTemplate) && !ammSle->isFieldPresent(sfAuctionSlot))
{
STObject auctionSlot = STObject::makeInnerObject(sfAuctionSlot);
ammSle->set(std::move(auctionSlot));
}
STObject& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
auctionSlot.setAccountID(sfAccount, account);
// current + sec in 24h
auto const expiration = std::chrono::duration_cast<std::chrono::seconds>(
view.header().parentCloseTime.time_since_epoch())
.count() +
TOTAL_TIME_SLOT_SECS;
auctionSlot.setFieldU32(sfExpiration, expiration);
auctionSlot.setFieldAmount(sfPrice, STAmount{lptIssue, 0});
// Set the fee
if (tfee != 0)
{
ammSle->setFieldU16(sfTradingFee, tfee);
}
else if (ammSle->isFieldPresent(sfTradingFee))
{
ammSle->makeFieldAbsent(sfTradingFee); // LCOV_EXCL_LINE
}
if (auto const dfee = tfee / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION)
{
auctionSlot.setFieldU16(sfDiscountedFee, dfee);
}
else if (auctionSlot.isFieldPresent(sfDiscountedFee))
{
auctionSlot.makeFieldAbsent(sfDiscountedFee); // LCOV_EXCL_LINE
}
}
Expected<bool, TER>
isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount)
{
// Liquidity Provider (LP) must have one LPToken trustline
std::uint8_t nLPTokenTrustLines = 0;
// There are at most two IOU trustlines. One or both could be to the LP
// if LP is the issuer, or a different account if LP is not an issuer.
// For instance, if AMM has two tokens USD and EUR and LP is not the issuer
// of the tokens then the trustlines are between AMM account and the issuer.
std::uint8_t nIOUTrustLines = 0;
// There is only one AMM object
bool hasAMM = false;
// AMM LP has at most three trustlines and only one AMM object must exist.
// If there are more than five objects then it's either an error or
// there are more than one LP. Ten pages should be sufficient to include
// five objects.
std::uint8_t limit = 10;
auto const root = keylet::ownerDir(ammIssue.account);
auto currentIndex = root;
// Iterate over AMM owner directory objects.
while (limit-- >= 1)
{
auto const ownerDir = view.read(currentIndex);
if (!ownerDir)
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
for (auto const& key : ownerDir->getFieldV256(sfIndexes))
{
auto const sle = view.read(keylet::child(key));
if (!sle)
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
// Only one AMM object
if (sle->getFieldU16(sfLedgerEntryType) == ltAMM)
{
if (hasAMM)
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
hasAMM = true;
continue;
}
if (sle->getFieldU16(sfLedgerEntryType) != ltRIPPLE_STATE)
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
auto const lowLimit = sle->getFieldAmount(sfLowLimit);
auto const highLimit = sle->getFieldAmount(sfHighLimit);
auto const isLPTrustline =
lowLimit.getIssuer() == lpAccount || highLimit.getIssuer() == lpAccount;
auto const isLPTokenTrustline =
lowLimit.issue() == ammIssue || highLimit.issue() == ammIssue;
// Liquidity Provider trustline
if (isLPTrustline)
{
// LPToken trustline
if (isLPTokenTrustline)
{
if (++nLPTokenTrustLines > 1)
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
}
else if (++nIOUTrustLines > 2)
{
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
}
}
// Another Liquidity Provider LPToken trustline
else if (isLPTokenTrustline)
{
return false;
}
else if (++nIOUTrustLines > 2)
{
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
}
}
auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
if (uNodeNext == 0)
{
if (nLPTokenTrustLines != 1 || nIOUTrustLines == 0 || nIOUTrustLines > 2)
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
return true;
}
currentIndex = keylet::page(root, uNodeNext);
}
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
}
Expected<bool, TER>
verifyAndAdjustLPTokenBalance(
Sandbox& sb,
STAmount const& lpTokens,
std::shared_ptr<SLE>& ammSle,
AccountID const& account)
{
auto const res = isOnlyLiquidityProvider(sb, lpTokens.issue(), account);
if (!res.has_value())
{
return Unexpected<TER>(res.error());
}
if (res.value())
{
if (withinRelativeDistance(
lpTokens, ammSle->getFieldAmount(sfLPTokenBalance), Number{1, -3}))
{
ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
sb.update(ammSle);
}
else
{
return Unexpected<TER>(tecAMM_INVALID_TOKENS);
}
}
return true;
}
} // namespace xrpl

View File

@@ -80,7 +80,7 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
auto const fullBalance = sle->getFieldAmount(sfBalance);
auto const balance = view.balanceHook(id, xrpAccount(), fullBalance);
auto const balance = view.balanceHookIOU(id, xrpAccount(), fullBalance);
STAmount const amount = (balance < reserve) ? STAmount{0} : balance - reserve;

View File

@@ -258,6 +258,13 @@ removeEmptyHolding(
(view.rules().enabled(fixSecurity3_1_3) && (*mptoken)[~sfLockedAmount].value_or(0) != 0))
return tecHAS_OBLIGATIONS;
// Don't delete if the token still has confidential balances
if (mptoken->isFieldPresent(sfConfidentialBalanceInbox) ||
mptoken->isFieldPresent(sfConfidentialBalanceSpending))
{
return tecHAS_OBLIGATIONS;
}
return authorizeMPToken(
view,
{}, // priorBalance
@@ -306,18 +313,11 @@ requireAuth(
return tefINTERNAL; // LCOV_EXCL_LINE
auto const asset = sleVault->at(sfAsset);
if (auto const err = std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return requireAuth(view, issue, account, authType);
}
else
{
return requireAuth(view, issue, account, authType, depth + 1);
}
},
asset.value());
if (auto const err = asset.visit(
[&](Issue const& issue) { return requireAuth(view, issue, account, authType); },
[&](MPTIssue const& issue) {
return requireAuth(view, issue, account, authType, depth + 1);
});
!isTesSuccess(err))
return err;
}
@@ -487,6 +487,21 @@ canTransfer(
return tesSUCCESS;
}
TER
canTrade(ReadView const& view, Asset const& asset)
{
return asset.visit(
[&](Issue const&) -> TER { return tesSUCCESS; },
[&](MPTIssue const& mptIssue) -> TER {
auto const sleIssuance = view.read(keylet::mptIssuance(mptIssue.getMptID()));
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
if (!sleIssuance->isFlag(lsfMPTCanTrade))
return tecNO_PERMISSION;
return tesSUCCESS;
});
}
TER
lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount, beast::Journal j)
{
@@ -770,4 +785,149 @@ createMPToken(
return tesSUCCESS;
}
TER
checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
beast::Journal j)
{
if (mptIssue.getIssuer() == holder)
return tesSUCCESS;
auto const mptIssuanceID = keylet::mptIssuance(mptIssue.getMptID());
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
if (!view.exists(mptokenID))
{
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0);
!isTesSuccess(err))
{
return err;
}
auto const sleAcct = view.peek(keylet::account(holder));
if (!sleAcct)
{
return tecINTERNAL;
}
adjustOwnerCount(view, sleAcct, 1, j);
}
return tesSUCCESS;
}
std::int64_t
maxMPTAmount(SLE const& sleIssuance)
{
return sleIssuance[~sfMaximumAmount].value_or(maxMPTokenAmount);
}
std::int64_t
availableMPTAmount(SLE const& sleIssuance)
{
auto const max = maxMPTAmount(sleIssuance);
auto const outstanding = sleIssuance[sfOutstandingAmount];
return max - outstanding;
}
std::int64_t
availableMPTAmount(ReadView const& view, MPTID const& mptID)
{
auto const sle = view.read(keylet::mptIssuance(mptID));
if (!sle)
Throw<std::runtime_error>(transHuman(tecINTERNAL));
return availableMPTAmount(*sle);
}
bool
isMPTOverflow(
std::int64_t sendAmount,
std::uint64_t outstandingAmount,
std::int64_t maximumAmount,
AllowMPTOverflow allowOverflow)
{
std::uint64_t const limit = (allowOverflow == AllowMPTOverflow::Yes)
? std::numeric_limits<std::uint64_t>::max()
: maximumAmount;
return (sendAmount > maximumAmount || outstandingAmount > (limit - sendAmount));
}
STAmount
issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue)
{
STAmount amount{issue};
auto const sle = view.read(keylet::mptIssuance(issue));
if (!sle)
return amount;
auto const available = availableMPTAmount(*sle);
return view.balanceHookSelfIssueMPT(issue, available);
}
void
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount)
{
auto const available = availableMPTAmount(view, issue);
view.issuerSelfDebitHookMPT(issue, amount, available);
}
static TER
checkMPTAllowed(ReadView const& view, TxType txType, Asset const& asset, AccountID const& accountID)
{
if (!asset.holds<MPTIssue>())
return tesSUCCESS;
auto const& issuanceID = asset.get<MPTIssue>().getMptID();
auto const validTx = txType == ttAMM_CREATE || txType == ttAMM_DEPOSIT ||
txType == ttAMM_WITHDRAW || txType == ttOFFER_CREATE || txType == ttCHECK_CREATE ||
txType == ttCHECK_CASH || txType == ttPAYMENT;
XRPL_ASSERT(validTx, "xrpl::checkMPTAllowed : all MPT tx or DEX");
if (!validTx)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const& issuer = asset.getIssuer();
if (!view.exists(keylet::account(issuer)))
return tecNO_ISSUER; // LCOV_EXCL_LINE
auto const issuanceKey = keylet::mptIssuance(issuanceID);
auto const issuanceSle = view.read(issuanceKey);
if (!issuanceSle)
return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE
auto const flags = issuanceSle->getFlags();
if ((flags & lsfMPTLocked) != 0u)
return tecLOCKED; // LCOV_EXCL_LINE
// Offer crossing and Payment
if ((flags & lsfMPTCanTrade) == 0)
return tecNO_PERMISSION;
if (accountID != issuer)
{
if ((flags & lsfMPTCanTransfer) == 0)
return tecNO_PERMISSION;
auto const mptSle = view.read(keylet::mptoken(issuanceKey.key, accountID));
// Allow to succeed since some tx create MPToken if it doesn't exist.
// Tx's have their own check for missing MPToken.
if (!mptSle)
return tesSUCCESS;
if (mptSle->isFlag(lsfMPTLocked))
return tecLOCKED;
}
return tesSUCCESS;
}
TER
checkMPTTxAllowed(
ReadView const& view,
TxType txType,
Asset const& asset,
AccountID const& accountID)
{
// use isDEXAllowed for payment/offer crossing
XRPL_ASSERT(txType != ttPAYMENT, "xrpl::checkMPTTxAllowed : not payment");
return checkMPTAllowed(view, txType, asset, accountID);
}
} // namespace xrpl

View File

@@ -855,15 +855,15 @@ tokenOfferCreatePreclaim(
if (view.rules().enabled(featureNFTokenMintOffer))
{
if (nftIssuer != amount.getIssuer() &&
!view.read(keylet::line(nftIssuer, amount.issue())))
!view.read(keylet::line(nftIssuer, amount.get<Issue>())))
return tecNO_LINE;
}
else if (!view.exists(keylet::line(nftIssuer, amount.issue())))
else if (!view.exists(keylet::line(nftIssuer, amount.get<Issue>())))
{
return tecNO_LINE;
}
if (isFrozen(view, nftIssuer, amount.getCurrency(), amount.getIssuer()))
if (isFrozen(view, nftIssuer, amount.get<Issue>().currency, amount.getIssuer()))
return tecFROZEN;
}
@@ -876,7 +876,7 @@ tokenOfferCreatePreclaim(
return tefNFTOKEN_IS_NOT_TRANSFERABLE;
}
if (isFrozen(view, acctID, amount.getCurrency(), amount.getIssuer()))
if (isFrozen(view, acctID, amount.get<Issue>().currency, amount.getIssuer()))
return tecFROZEN;
// If this is an offer to buy the token, the account must have the

View File

@@ -33,11 +33,14 @@ creditLimit(
if (sleRippleState)
{
result = sleRippleState->getFieldAmount(account < issuer ? sfLowLimit : sfHighLimit);
result.setIssuer(account);
result.get<Issue>().account = account;
}
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditLimit : result issuer match");
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditLimit : result currency match");
XRPL_ASSERT(
result.get<Issue>().currency == currency,
"xrpl::creditLimit : result currency "
"match");
return result;
}
@@ -63,11 +66,14 @@ creditBalance(
result = sleRippleState->getFieldAmount(sfBalance);
if (account < issuer)
result.negate();
result.setIssuer(account);
result.get<Issue>().account = account;
}
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditBalance : result issuer match");
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditBalance : result currency match");
XRPL_ASSERT(
result.get<Issue>().currency == currency,
"xrpl::creditBalance : result currency "
"match");
return result;
}
@@ -222,7 +228,7 @@ trustCreate(
sleRippleState->setFieldAmount(bSetHigh ? sfHighLimit : sfLowLimit, saLimit);
sleRippleState->setFieldAmount(
bSetHigh ? sfLowLimit : sfHighLimit,
STAmount(Issue{saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID}));
STAmount(Issue{saBalance.get<Issue>().currency, bSetDst ? uSrcAccountID : uDstAccountID}));
if (uQualityIn != 0u)
sleRippleState->setFieldU32(bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
@@ -261,7 +267,7 @@ trustCreate(
// ONLY: Create ripple balance.
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
view.creditHook(uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed());
view.creditHookIOU(uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed());
return tesSUCCESS;
}
@@ -367,7 +373,7 @@ issueIOU(
"xrpl::issueIOU : neither account nor issuer is XRP");
// Consistency check
XRPL_ASSERT(issue == amount.issue(), "xrpl::issueIOU : matching issue");
XRPL_ASSERT(issue == amount.get<Issue>(), "xrpl::issueIOU : matching issue");
// Can't send to self!
XRPL_ASSERT(issue.account != account, "xrpl::issueIOU : not issuer account");
@@ -392,7 +398,7 @@ issueIOU(
auto const must_delete = updateTrustLine(
view, state, bSenderHigh, issue.account, start_balance, final_balance, j);
view.creditHook(issue.account, account, amount, start_balance);
view.creditHookIOU(issue.account, account, amount, start_balance);
if (bSenderHigh)
final_balance.negate();
@@ -422,7 +428,7 @@ issueIOU(
STAmount const limit(Issue{issue.currency, account});
STAmount final_balance = amount;
final_balance.setIssuer(noAccount());
final_balance.get<Issue>().account = noAccount();
auto const receiverAccount = view.peek(keylet::account(account));
if (!receiverAccount)
@@ -461,7 +467,7 @@ redeemIOU(
"xrpl::redeemIOU : neither account nor issuer is XRP");
// Consistency check
XRPL_ASSERT(issue == amount.issue(), "xrpl::redeemIOU : matching issue");
XRPL_ASSERT(issue == amount.get<Issue>(), "xrpl::redeemIOU : matching issue");
// Can't send to self!
XRPL_ASSERT(issue.account != account, "xrpl::redeemIOU : not issuer account");
@@ -484,7 +490,7 @@ redeemIOU(
auto const must_delete =
updateTrustLine(view, state, bSenderHigh, account, start_balance, final_balance, j);
view.creditHook(account, issue.account, amount, start_balance);
view.creditHookIOU(account, issue.account, amount, start_balance);
if (bSenderHigh)
final_balance.negate();
@@ -757,4 +763,20 @@ deleteAMMTrustLine(
return tesSUCCESS;
}
TER
deleteAMMMPToken(
ApplyView& view,
std::shared_ptr<SLE> sleMpt,
AccountID const& ammAccountID,
beast::Journal j)
{
if (!view.dirRemove(
keylet::ownerDir(ammAccountID), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tefBAD_LEDGER; // LCOV_EXCL_LINE
view.erase(sleMpt);
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -23,8 +23,8 @@ bool
isLPTokenFrozen(
ReadView const& view,
AccountID const& account,
Issue const& asset,
Issue const& asset2);
Asset const& asset,
Asset const& asset2);
//------------------------------------------------------------------------------
//
@@ -35,18 +35,9 @@ isLPTokenFrozen(
bool
isGlobalFrozen(ReadView const& view, Asset const& asset)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return isGlobalFrozen(view, issue.getIssuer());
}
else
{
return isGlobalFrozen(view, issue);
}
},
asset.value());
return asset.visit(
[&](Issue const& issue) { return isGlobalFrozen(view, issue.getIssuer()); },
[&](MPTIssue const& issue) { return isGlobalFrozen(view, issue); });
}
bool
@@ -103,18 +94,9 @@ isAnyFrozen(
Asset const& asset,
int depth)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return isAnyFrozen(view, accounts, issue);
}
else
{
return isAnyFrozen(view, accounts, issue, depth);
}
},
asset.value());
return asset.visit(
[&](Issue const& issue) { return isAnyFrozen(view, accounts, issue); },
[&](MPTIssue const& issue) { return isAnyFrozen(view, accounts, issue, depth); });
}
bool
@@ -190,11 +172,7 @@ getLineIfUsable(
auto const sleAmm = view.read(keylet::amm((*sleIssuer)[sfAMMID]));
if (!sleAmm ||
isLPTokenFrozen(
view,
account,
(*sleAmm)[sfAsset].get<Issue>(),
(*sleAmm)[sfAsset2].get<Issue>()))
isLPTokenFrozen(view, account, (*sleAmm)[sfAsset], (*sleAmm)[sfAsset2]))
{
return nullptr;
}
@@ -230,7 +208,7 @@ getTrustLineBalance(
{
amount += sle->getFieldAmount(oppositeField);
}
amount.setIssuer(issuer);
amount.get<Issue>().account = issuer;
}
else
{
@@ -240,7 +218,7 @@ getTrustLineBalance(
JLOG(j.trace()) << "getTrustLineBalance:" << " account=" << to_string(account)
<< " amount=" << amount.getFullText();
return view.balanceHook(account, issuer, amount);
return view.balanceHookIOU(account, issuer, amount);
}
STAmount
@@ -298,6 +276,9 @@ accountHolds(
SpendableHandling includeFullBalance)
{
bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
STAmount amount{mptIssue};
auto const& issuer = mptIssue.getIssuer();
bool const mptokensV2 = view.rules().enabled(featureMPTokensV2);
if (returnSpendable && account == mptIssue.getIssuer())
{
@@ -307,16 +288,14 @@ accountHolds(
if (!issuance)
{
return STAmount{mptIssue};
return amount;
}
return STAmount{
mptIssue,
issuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount) -
issuance->at(sfOutstandingAmount)};
auto const available = availableMPTAmount(*issuance);
if (!mptokensV2)
return STAmount{mptIssue, available};
return view.balanceHookMPT(issuer, mptIssue, available);
}
STAmount amount;
auto const sleMpt = view.read(keylet::mptoken(mptIssue.getMptID(), account));
if (!sleMpt)
@@ -334,7 +313,8 @@ accountHolds(
// Only if auth check is needed, as it needs to do an additional read
// operation. Note featureSingleAssetVault will affect error codes.
if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
view.rules().enabled(featureSingleAssetVault))
(view.rules().enabled(featureSingleAssetVault) ||
view.rules().enabled(featureConfidentialTransfer)))
{
if (auto const err = requireAuth(view, mptIssue, account, AuthType::StrongAuth);
!isTesSuccess(err))
@@ -352,6 +332,8 @@ accountHolds(
}
}
if (view.rules().enabled(featureMPTokensV2))
return view.balanceHookMPT(account, mptIssue, amount.mpt().value());
return amount;
}
@@ -365,19 +347,14 @@ accountHolds(
beast::Journal j,
SpendableHandling includeFullBalance)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& value) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return accountHolds(view, account, value, zeroIfFrozen, j, includeFullBalance);
}
else if constexpr (std::is_same_v<TIss, MPTIssue>)
{
return accountHolds(
view, account, value, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance);
}
return asset.visit(
[&](Issue const& issue) {
return accountHolds(view, account, issue, zeroIfFrozen, j, includeFullBalance);
},
asset.value());
[&](MPTIssue const& issue) {
return accountHolds(
view, account, issue, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance);
});
}
STAmount
@@ -388,28 +365,38 @@ accountFunds(
FreezeHandling freezeHandling,
beast::Journal j)
{
XRPL_ASSERT(saDefault.holds<Issue>(), "xrpl::accountFunds: saDefault holds Issue");
if (!saDefault.native() && saDefault.getIssuer() == id)
return saDefault;
return accountHolds(
view, id, saDefault.getCurrency(), saDefault.getIssuer(), freezeHandling, j);
view, id, saDefault.get<Issue>().currency, saDefault.getIssuer(), freezeHandling, j);
}
STAmount
accountFunds(
ReadView const& view,
AccountID const& id,
STAmount const& saDefault,
FreezeHandling freezeHandling,
AuthHandling authHandling,
beast::Journal j)
{
return saDefault.asset().visit(
[&](Issue const&) { return accountFunds(view, id, saDefault, freezeHandling, j); },
[&](MPTIssue const&) {
return accountHolds(
view, id, saDefault.asset(), freezeHandling, authHandling, j, shFULL_BALANCE);
});
}
Rate
transferRate(ReadView const& view, STAmount const& amount)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return transferRate(view, issue.getIssuer());
}
else
{
return transferRate(view, issue.getMptID());
}
},
amount.asset().value());
return amount.asset().visit(
[&](Issue const& issue) { return transferRate(view, issue.getIssuer()); },
[&](MPTIssue const& issue) { return transferRate(view, issue.getMptID()); });
}
//------------------------------------------------------------------------------
@@ -522,7 +509,7 @@ directSendNoFeeIOU(
beast::Journal j)
{
AccountID const& issuer = saAmount.getIssuer();
Currency const& currency = saAmount.getCurrency();
Currency const& currency = saAmount.get<Issue>().currency;
// Make sure issuer is involved.
XRPL_ASSERT(
@@ -551,7 +538,7 @@ directSendNoFeeIOU(
if (bSenderHigh)
saBalance.negate(); // Put balance in sender terms.
view.creditHook(uSenderID, uReceiverID, saAmount, saBalance);
view.creditHookIOU(uSenderID, uReceiverID, saAmount, saBalance);
STAmount const saBefore = saBalance;
@@ -622,7 +609,7 @@ directSendNoFeeIOU(
STAmount const saReceiverLimit(Issue{currency, uReceiverID});
STAmount saBalance{saAmount};
saBalance.setIssuer(noAccount());
saBalance.get<Issue>().account = noAccount();
JLOG(j.debug()) << "directSendNoFeeIOU: "
"create line: "
@@ -859,7 +846,7 @@ accountSendIOU(
else
{
auto const sndBal = sender->getFieldAmount(sfBalance);
view.creditHook(uSenderID, xrpAccount(), saAmount, sndBal);
view.creditHookIOU(uSenderID, xrpAccount(), saAmount, sndBal);
// Decrement XRP balance.
sender->setFieldAmount(sfBalance, sndBal - saAmount);
@@ -872,7 +859,7 @@ accountSendIOU(
// Increment XRP balance.
auto const rcvBal = receiver->getFieldAmount(sfBalance);
receiver->setFieldAmount(sfBalance, rcvBal + saAmount);
view.creditHook(xrpAccount(), uReceiverID, saAmount, -rcvBal);
view.creditHookIOU(xrpAccount(), uReceiverID, saAmount, -rcvBal);
view.update(receiver);
}
@@ -975,7 +962,7 @@ accountSendMultiIOU(
// Increment XRP balance.
auto const rcvBal = receiver->getFieldAmount(sfBalance);
receiver->setFieldAmount(sfBalance, rcvBal + amount);
view.creditHook(xrpAccount(), receiverID, amount, -rcvBal);
view.creditHookIOU(xrpAccount(), receiverID, amount, -rcvBal);
view.update(receiver);
@@ -1003,7 +990,7 @@ accountSendMultiIOU(
return TER{tecFAILED_PROCESSING};
}
auto const sndBal = sender->getFieldAmount(sfBalance);
view.creditHook(senderID, xrpAccount(), takeFromSender, sndBal);
view.creditHookIOU(senderID, xrpAccount(), takeFromSender, sndBal);
// Decrement XRP balance.
sender->setFieldAmount(sfBalance, sndBal - takeFromSender);
@@ -1037,9 +1024,20 @@ directSendNoFeeMPT(
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
auto const maxAmount = maxMPTAmount(*sleIssuance);
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
auto const available = availableMPTAmount(*sleIssuance);
auto const amt = saAmount.mpt().value();
if (uSenderID == issuer)
{
(*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
if (view.rules().enabled(featureMPTokensV2))
{
if (isMPTOverflow(amt, outstanding, maxAmount, AllowMPTOverflow::Yes))
return tecPATH_DRY;
}
(*sleIssuance)[sfOutstandingAmount] += amt;
view.update(sleIssuance);
}
else
@@ -1047,11 +1045,11 @@ directSendNoFeeMPT(
auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
if (auto sle = view.peek(mptokenID))
{
auto const amt = sle->getFieldU64(sfMPTAmount);
auto const pay = saAmount.mpt().value();
if (amt < pay)
auto const senderBalance = sle->getFieldU64(sfMPTAmount);
if (senderBalance < amt)
return tecINSUFFICIENT_FUNDS;
(*sle)[sfMPTAmount] = amt - pay;
view.creditHookMPT(uSenderID, uReceiverID, saAmount, (*sle)[sfMPTAmount], available);
(*sle)[sfMPTAmount] = senderBalance - amt;
view.update(sle);
}
else
@@ -1062,11 +1060,9 @@ directSendNoFeeMPT(
if (uReceiverID == issuer)
{
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
auto const redeem = saAmount.mpt().value();
if (outstanding >= redeem)
if (outstanding >= amt)
{
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - amt);
view.update(sleIssuance);
}
else
@@ -1079,7 +1075,8 @@ directSendNoFeeMPT(
auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
if (auto sle = view.peek(mptokenID))
{
(*sle)[sfMPTAmount] += saAmount.mpt().value();
view.creditHookMPT(uSenderID, uReceiverID, saAmount, (*sle)[sfMPTAmount], available);
(*sle)[sfMPTAmount] += amt;
view.update(sle);
}
else
@@ -1099,7 +1096,8 @@ directSendNoLimitMPT(
STAmount const& saAmount,
STAmount& saActual,
beast::Journal j,
WaiveTransferFee waiveFee)
WaiveTransferFee waiveFee,
AllowMPTOverflow allowOverflow)
{
XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::directSendNoLimitMPT : sender is not receiver");
@@ -1117,9 +1115,13 @@ directSendNoLimitMPT(
if (uSenderID == issuer)
{
auto const sendAmount = saAmount.mpt().value();
auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
if (sendAmount > maximumAmount ||
sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
auto const maxAmount = maxMPTAmount(*sle);
auto const outstanding = sle->getFieldU64(sfOutstandingAmount);
auto const mptokensV2 = view.rules().enabled(featureMPTokensV2);
allowOverflow = (allowOverflow == AllowMPTOverflow::Yes && mptokensV2)
? AllowMPTOverflow::Yes
: AllowMPTOverflow::No;
if (isMPTOverflow(sendAmount, outstanding, maxAmount, allowOverflow))
return tecPATH_DRY;
}
@@ -1233,7 +1235,8 @@ directSendNoLimitMultiMPT(
}
// Direct send: redeeming MPTs and/or sending own MPTs.
if (auto const ter = directSendNoFeeMPT(view, senderID, receiverID, amount, j))
if (auto const ter = directSendNoFeeMPT(view, senderID, receiverID, amount, j);
!isTesSuccess(ter))
return ter;
actual += amount;
// Do not add amount to takeFromSender, because directSendNoFeeMPT took it.
@@ -1252,13 +1255,15 @@ directSendNoLimitMultiMPT(
<< to_string(receiverID) << " : deliver=" << amount.getFullText()
<< " cost=" << actualSend.getFullText();
if (auto const terResult = directSendNoFeeMPT(view, issuer, receiverID, amount, j))
return terResult;
if (auto const ter = directSendNoFeeMPT(view, issuer, receiverID, amount, j);
!isTesSuccess(ter))
return ter;
}
if (senderID != issuer && takeFromSender)
{
if (TER const terResult = directSendNoFeeMPT(view, senderID, issuer, takeFromSender, j))
return terResult;
if (auto const ter = directSendNoFeeMPT(view, senderID, issuer, takeFromSender, j);
!isTesSuccess(ter))
return ter;
}
return tesSUCCESS;
@@ -1271,7 +1276,8 @@ accountSendMPT(
AccountID const& uReceiverID,
STAmount const& saAmount,
beast::Journal j,
WaiveTransferFee waiveFee)
WaiveTransferFee waiveFee,
AllowMPTOverflow allowOverflow)
{
XRPL_ASSERT(
saAmount >= beast::zero && saAmount.holds<MPTIssue>(),
@@ -1285,7 +1291,8 @@ accountSendMPT(
STAmount saActual{saAmount.asset()};
return directSendNoLimitMPT(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
return directSendNoLimitMPT(
view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee, allowOverflow);
}
static TER
@@ -1317,19 +1324,14 @@ directSendNoFee(
bool bCheckIssuer,
beast::Journal j)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
}
else
{
XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer");
return directSendNoFeeMPT(view, uSenderID, uReceiverID, saAmount, j);
}
return saAmount.asset().visit(
[&](Issue const&) {
return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
},
saAmount.asset().value());
[&](MPTIssue const&) {
XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer");
return directSendNoFeeMPT(view, uSenderID, uReceiverID, saAmount, j);
});
}
TER
@@ -1339,20 +1341,17 @@ accountSend(
AccountID const& uReceiverID,
STAmount const& saAmount,
beast::Journal j,
WaiveTransferFee waiveFee)
WaiveTransferFee waiveFee,
AllowMPTOverflow allowOverflow)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee);
}
else
{
return accountSendMPT(view, uSenderID, uReceiverID, saAmount, j, waiveFee);
}
return saAmount.asset().visit(
[&](Issue const&) {
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee);
},
saAmount.asset().value());
[&](MPTIssue const&) {
return accountSendMPT(
view, uSenderID, uReceiverID, saAmount, j, waiveFee, allowOverflow);
});
}
TER
@@ -1366,18 +1365,13 @@ accountSendMulti(
{
XRPL_ASSERT_PARTS(
receivers.size() > 1, "xrpl::accountSendMulti", "multiple recipients provided");
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return accountSendMultiIOU(view, senderID, issue, receivers, j, waiveFee);
}
else
{
return accountSendMultiMPT(view, senderID, issue, receivers, j, waiveFee);
}
return asset.visit(
[&](Issue const& issue) {
return accountSendMultiIOU(view, senderID, issue, receivers, j, waiveFee);
},
asset.value());
[&](MPTIssue const& issue) {
return accountSendMultiMPT(view, senderID, issue, receivers, j, waiveFee);
});
}
TER

View File

@@ -21,12 +21,23 @@
namespace xrpl {
Currency
ammLPTCurrency(Currency const& cur1, Currency const& cur2)
ammLPTCurrency(Asset const& asset1, Asset const& asset2)
{
// AMM LPToken is 0x03 plus 19 bytes of the hash
std::int32_t constexpr AMMCurrencyCode = 0x03;
auto const [minC, maxC] = std::minmax(cur1, cur2);
auto const hash = sha512Half(minC, maxC);
auto const& [minA, maxA] = std::minmax(asset1, asset2);
uint256 const hash = std::visit(
[](auto&& issue1, auto&& issue2) {
auto fromIss = []<ValidIssueType T>(T const& issue) {
if constexpr (std::is_same_v<T, Issue>)
return issue.currency;
if constexpr (std::is_same_v<T, MPTIssue>)
return issue.getMptID();
};
return sha512Half(fromIss(issue1), fromIss(issue2));
},
minA.value(),
maxA.value());
Currency currency;
*currency.begin() = AMMCurrencyCode;
std::copy(hash.begin(), hash.begin() + currency.size() - 1, currency.begin() + 1);
@@ -34,34 +45,45 @@ ammLPTCurrency(Currency const& cur1, Currency const& cur2)
}
Issue
ammLPTIssue(Currency const& cur1, Currency const& cur2, AccountID const& ammAccountID)
ammLPTIssue(Asset const& asset1, Asset const& asset2, AccountID const& ammAccountID)
{
return Issue(ammLPTCurrency(cur1, cur2), ammAccountID);
return Issue(ammLPTCurrency(asset1, asset2), ammAccountID);
}
NotTEC
invalidAMMAsset(Issue const& issue, std::optional<std::pair<Issue, Issue>> const& pair)
invalidAMMAsset(Asset const& asset, std::optional<std::pair<Asset, Asset>> const& pair)
{
if (badCurrency() == issue.currency)
return temBAD_CURRENCY;
if (isXRP(issue) && issue.account.isNonZero())
return temBAD_ISSUER;
if (pair && issue != pair->first && issue != pair->second)
auto const err = asset.visit(
[](MPTIssue const& issue) -> std::optional<NotTEC> {
if (issue.getIssuer() == beast::zero)
return temBAD_MPT;
return std::nullopt;
},
[](Issue const& issue) -> std::optional<NotTEC> {
if (badCurrency() == issue.currency)
return temBAD_CURRENCY;
if (isXRP(issue) && issue.getIssuer().isNonZero())
return temBAD_ISSUER;
return std::nullopt;
});
if (err)
return *err;
if (pair && asset != pair->first && asset != pair->second)
return temBAD_AMM_TOKENS;
return tesSUCCESS;
}
NotTEC
invalidAMMAssetPair(
Issue const& issue1,
Issue const& issue2,
std::optional<std::pair<Issue, Issue>> const& pair)
Asset const& asset1,
Asset const& asset2,
std::optional<std::pair<Asset, Asset>> const& pair)
{
if (issue1 == issue2)
if (asset1 == asset2)
return temBAD_AMM_TOKENS;
if (auto const res = invalidAMMAsset(issue1, pair))
if (auto const res = invalidAMMAsset(asset1, pair))
return res;
if (auto const res = invalidAMMAsset(issue2, pair))
if (auto const res = invalidAMMAsset(asset2, pair))
return res;
return tesSUCCESS;
}
@@ -69,10 +91,10 @@ invalidAMMAssetPair(
NotTEC
invalidAMMAmount(
STAmount const& amount,
std::optional<std::pair<Issue, Issue>> const& pair,
std::optional<std::pair<Asset, Asset>> const& pair,
bool validZero)
{
if (auto const res = invalidAMMAsset(amount.issue(), pair))
if (auto const res = invalidAMMAsset(amount.asset(), pair))
return res;
if (amount < beast::zero || (!validZero && amount == beast::zero))
return temBAD_AMOUNT;

View File

@@ -62,4 +62,11 @@ assetFromJson(Json::Value const& v)
return mptIssueFromJson(v);
}
std::ostream&
operator<<(std::ostream& os, Asset const& x)
{
std::visit([&]<ValidIssueType TIss>(TIss const& issue) { os << issue; }, x.value());
return os;
}
} // namespace xrpl

View File

@@ -0,0 +1,445 @@
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Protocol.h>
#include <openssl/rand.h>
#include <utility/mpt_utility.h>
namespace xrpl {
/**
* @brief Converts an XRPL AccountID to mpt-crypto lib C struct.
*
* @param account The AccountID.
* @return The equivalent mpt-crypto lib account_id struct.
*/
account_id
toAccountId(AccountID const& account)
{
account_id res;
std::memcpy(res.bytes, account.data(), kMPT_ACCOUNT_ID_SIZE);
return res;
}
/**
* @brief Converts an XRPL uint192 to mpt-crypto lib C struct.
*
* @param i The XRPL MPTokenIssuance ID.
* @return The equivalent mpt-crypto lib mpt_issuance_id struct.
*/
mpt_issuance_id
toIssuanceId(uint192 const& issuance)
{
mpt_issuance_id res;
std::memcpy(res.bytes, issuance.data(), kMPT_ISSUANCE_ID_SIZE);
return res;
}
uint256
getSendContextHash(
AccountID const& account,
uint192 const& issuanceID,
std::uint32_t sequence,
AccountID const& destination,
std::uint32_t version)
{
uint256 result;
mpt_get_send_context_hash(
toAccountId(account),
toIssuanceId(issuanceID),
sequence,
toAccountId(destination),
version,
result.data());
return result;
}
uint256
getClawbackContextHash(
AccountID const& account,
uint192 const& issuanceID,
std::uint32_t sequence,
AccountID const& holder)
{
uint256 result;
mpt_get_clawback_context_hash(
toAccountId(account),
toIssuanceId(issuanceID),
sequence,
toAccountId(holder),
result.data());
return result;
}
uint256
getConvertContextHash(AccountID const& account, uint192 const& issuanceID, std::uint32_t sequence)
{
uint256 result;
mpt_get_convert_context_hash(
toAccountId(account), toIssuanceId(issuanceID), sequence, result.data());
return result;
}
uint256
getConvertBackContextHash(
AccountID const& account,
uint192 const& issuanceID,
std::uint32_t sequence,
std::uint32_t version)
{
uint256 result;
mpt_get_convert_back_context_hash(
toAccountId(account), toIssuanceId(issuanceID), sequence, version, result.data());
return result;
}
std::optional<EcPair>
makeEcPair(Slice const& buffer)
{
if (buffer.length() != 2 * ecGamalEncryptedLength)
return std::nullopt; // LCOV_EXCL_LINE
auto parsePubKey = [](Slice const& slice, secp256k1_pubkey& out) {
return secp256k1_ec_pubkey_parse(
secp256k1Context(),
&out,
reinterpret_cast<unsigned char const*>(slice.data()),
slice.length());
};
Slice const s1{buffer.data(), ecGamalEncryptedLength};
Slice const s2{buffer.data() + ecGamalEncryptedLength, ecGamalEncryptedLength};
EcPair pair{};
if (parsePubKey(s1, pair.c1) != 1 || parsePubKey(s2, pair.c2) != 1)
return std::nullopt;
return pair;
}
std::optional<Buffer>
serializeEcPair(EcPair const& pair)
{
auto serializePubKey = [](secp256k1_pubkey const& pub, unsigned char* out) {
size_t outLen = ecGamalEncryptedLength; // 33 bytes
int const ret = secp256k1_ec_pubkey_serialize(
secp256k1Context(), out, &outLen, &pub, SECP256K1_EC_COMPRESSED);
return ret == 1 && outLen == ecGamalEncryptedLength;
};
Buffer buffer(ecGamalEncryptedTotalLength);
unsigned char* ptr = buffer.data();
bool const res1 = serializePubKey(pair.c1, ptr);
bool const res2 = serializePubKey(pair.c2, ptr + ecGamalEncryptedLength);
if (!res1 || !res2)
return std::nullopt;
return buffer;
}
bool
isValidCiphertext(Slice const& buffer)
{
return makeEcPair(buffer).has_value();
}
bool
isValidCompressedECPoint(Slice const& buffer)
{
if (buffer.size() != compressedECPointLength)
return false;
// Compressed EC points must start with 0x02 or 0x03
if (buffer[0] != ecCompressedPrefixEvenY && buffer[0] != ecCompressedPrefixOddY)
return false;
secp256k1_pubkey point;
return secp256k1_ec_pubkey_parse(secp256k1Context(), &point, buffer.data(), buffer.size()) == 1;
}
std::optional<Buffer>
homomorphicAdd(Slice const& a, Slice const& b)
{
if (a.length() != ecGamalEncryptedTotalLength || b.length() != ecGamalEncryptedTotalLength)
return std::nullopt;
auto const pairA = makeEcPair(a);
auto const pairB = makeEcPair(b);
if (!pairA || !pairB)
return std::nullopt;
EcPair sum{};
if (auto res = secp256k1_elgamal_add(
secp256k1Context(), &sum.c1, &sum.c2, &pairA->c1, &pairA->c2, &pairB->c1, &pairB->c2);
res != 1)
{
return std::nullopt;
}
return serializeEcPair(sum);
}
std::optional<Buffer>
homomorphicSubtract(Slice const& a, Slice const& b)
{
if (a.length() != ecGamalEncryptedTotalLength || b.length() != ecGamalEncryptedTotalLength)
return std::nullopt;
auto const pairA = makeEcPair(a);
auto const pairB = makeEcPair(b);
if (!pairA || !pairB)
return std::nullopt;
EcPair diff{};
if (auto res = secp256k1_elgamal_subtract(
secp256k1Context(), &diff.c1, &diff.c2, &pairA->c1, &pairA->c2, &pairB->c1, &pairB->c2);
res != 1)
{
return std::nullopt;
}
return serializeEcPair(diff);
}
Buffer
generateBlindingFactor()
{
unsigned char blindingFactor[ecBlindingFactorLength];
// todo: might need to be updated using another RNG
if (RAND_bytes(blindingFactor, ecBlindingFactorLength) != 1)
Throw<std::runtime_error>("Failed to generate random number");
return Buffer(blindingFactor, ecBlindingFactorLength);
}
std::optional<Buffer>
encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindingFactor)
{
if (blindingFactor.size() != ecBlindingFactorLength || pubKeySlice.size() != ecPubKeyLength)
return std::nullopt;
Buffer out(ecGamalEncryptedTotalLength);
if (mpt_encrypt_amount(amt, pubKeySlice.data(), blindingFactor.data(), out.data()) != 0)
return std::nullopt;
return out;
}
std::optional<Buffer>
encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, MPTID const& mptId)
{
if (pubKeySlice.size() != ecPubKeyLength)
return std::nullopt; // LCOV_EXCL_LINE
EcPair pair{};
secp256k1_pubkey pubKey;
if (auto res = secp256k1_ec_pubkey_parse(
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
res != 1)
{
return std::nullopt; // LCOV_EXCL_LINE
}
if (auto res = generate_canonical_encrypted_zero(
secp256k1Context(), &pair.c1, &pair.c2, &pubKey, account.data(), mptId.data());
res != 1)
{
return std::nullopt; // LCOV_EXCL_LINE
}
return serializeEcPair(pair);
}
TER
verifyRevealedAmount(
uint64_t const amount,
Slice const& blindingFactor,
ConfidentialRecipient const& holder,
ConfidentialRecipient const& issuer,
std::optional<ConfidentialRecipient> const& auditor)
{
if (blindingFactor.size() != ecBlindingFactorLength ||
holder.publicKey.size() != ecPubKeyLength ||
holder.encryptedAmount.size() != ecGamalEncryptedTotalLength ||
issuer.publicKey.size() != ecPubKeyLength ||
issuer.encryptedAmount.size() != ecGamalEncryptedTotalLength)
return tecINTERNAL; // LCOV_EXCL_LINE
auto toParticipant = [](ConfidentialRecipient const& r) {
mpt_confidential_participant p;
std::memcpy(p.pubkey, r.publicKey.data(), kMPT_PUBKEY_SIZE);
std::memcpy(p.ciphertext, r.encryptedAmount.data(), kMPT_ELGAMAL_TOTAL_SIZE);
return p;
};
auto const holderP = toParticipant(holder);
auto const issuerP = toParticipant(issuer);
mpt_confidential_participant auditorP;
mpt_confidential_participant const* auditorPtr = nullptr;
if (auditor)
{
if (auditor->publicKey.size() != ecPubKeyLength ||
auditor->encryptedAmount.size() != ecGamalEncryptedTotalLength)
return tecINTERNAL; // LCOV_EXCL_LINE
auditorP = toParticipant(*auditor);
auditorPtr = &auditorP;
}
if (mpt_verify_revealed_amount(amount, blindingFactor.data(), &holderP, &issuerP, auditorPtr) !=
0)
return tecBAD_PROOF;
return tesSUCCESS;
}
NotTEC
checkEncryptedAmountFormat(STObject const& object)
{
// Current usage of this function is only for ConfidentialMPTConvert and
// ConfidentialMPTConvertBack transactions, which already enforce that these fields
// are present.
if (!object.isFieldPresent(sfHolderEncryptedAmount) ||
!object.isFieldPresent(sfIssuerEncryptedAmount))
return temMALFORMED; // LCOV_EXCL_LINE
if (object[sfHolderEncryptedAmount].length() != ecGamalEncryptedTotalLength ||
object[sfIssuerEncryptedAmount].length() != ecGamalEncryptedTotalLength)
return temBAD_CIPHERTEXT;
bool const hasAuditor = object.isFieldPresent(sfAuditorEncryptedAmount);
if (hasAuditor && object[sfAuditorEncryptedAmount].length() != ecGamalEncryptedTotalLength)
return temBAD_CIPHERTEXT;
if (!isValidCiphertext(object[sfHolderEncryptedAmount]) ||
!isValidCiphertext(object[sfIssuerEncryptedAmount]))
return temBAD_CIPHERTEXT;
if (hasAuditor && !isValidCiphertext(object[sfAuditorEncryptedAmount]))
return temBAD_CIPHERTEXT;
return tesSUCCESS;
}
TER
verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash)
{
if (proofSlice.size() != ecSchnorrProofLength || pubKeySlice.size() != ecPubKeyLength)
return tecINTERNAL; // LCOV_EXCL_LINE
if (mpt_verify_convert_proof(proofSlice.data(), pubKeySlice.data(), contextHash.data()) != 0)
return tecBAD_PROOF;
return tesSUCCESS;
}
TER
verifyClawbackEqualityProof(
uint64_t const amount,
Slice const& proof,
Slice const& pubKeySlice,
Slice const& ciphertext,
uint256 const& contextHash)
{
if (ciphertext.size() != ecGamalEncryptedTotalLength || pubKeySlice.size() != ecPubKeyLength ||
proof.size() != ecEqualityProofLength)
return tecINTERNAL; // LCOV_EXCL_LINE
if (mpt_verify_clawback_proof(
proof.data(), amount, pubKeySlice.data(), ciphertext.data(), contextHash.data()) != 0)
return tecBAD_PROOF;
return tesSUCCESS;
}
TER
verifySendProof(
Slice const& proof,
ConfidentialRecipient const& sender,
ConfidentialRecipient const& destination,
ConfidentialRecipient const& issuer,
std::optional<ConfidentialRecipient> const& auditor,
Slice const& spendingBalance,
Slice const& amountCommitment,
Slice const& balanceCommitment,
uint256 const& contextHash)
{
auto const recipientCount = getConfidentialRecipientCount(auditor.has_value());
auto const expectedProofSize = getEqualityProofSize(recipientCount) +
2 * ecPedersenProofLength + ecDoubleBulletproofLength;
if (proof.size() != expectedProofSize || sender.publicKey.size() != ecPubKeyLength ||
sender.encryptedAmount.size() != ecGamalEncryptedTotalLength ||
destination.publicKey.size() != ecPubKeyLength ||
destination.encryptedAmount.size() != ecGamalEncryptedTotalLength ||
issuer.publicKey.size() != ecPubKeyLength ||
issuer.encryptedAmount.size() != ecGamalEncryptedTotalLength ||
spendingBalance.size() != ecGamalEncryptedTotalLength ||
amountCommitment.size() != ecPedersenCommitmentLength ||
balanceCommitment.size() != ecPedersenCommitmentLength)
return tecINTERNAL; // LCOV_EXCL_LINE
auto makeParticipant = [](ConfidentialRecipient const& r) {
mpt_confidential_participant p;
std::memcpy(p.pubkey, r.publicKey.data(), kMPT_PUBKEY_SIZE);
std::memcpy(p.ciphertext, r.encryptedAmount.data(), kMPT_ELGAMAL_TOTAL_SIZE);
return p;
};
std::vector<mpt_confidential_participant> participants(recipientCount);
participants[0] = makeParticipant(sender);
participants[1] = makeParticipant(destination);
participants[2] = makeParticipant(issuer);
if (auditor)
{
if (auditor->publicKey.size() != ecPubKeyLength ||
auditor->encryptedAmount.size() != ecGamalEncryptedTotalLength)
return tecINTERNAL;
participants[3] = makeParticipant(*auditor);
}
if (mpt_verify_send_proof(
proof.data(),
proof.size(),
participants.data(),
static_cast<uint8_t>(recipientCount),
spendingBalance.data(),
amountCommitment.data(),
balanceCommitment.data(),
contextHash.data()) != 0)
return tecBAD_PROOF;
return tesSUCCESS;
}
TER
verifyConvertBackProof(
Slice const& proof,
Slice const& pubKeySlice,
Slice const& spendingBalance,
Slice const& balanceCommitment,
uint64_t amount,
uint256 const& contextHash)
{
if (proof.size() != ecPedersenProofLength + ecSingleBulletproofLength ||
pubKeySlice.size() != ecPubKeyLength ||
spendingBalance.size() != ecGamalEncryptedTotalLength ||
balanceCommitment.size() != ecPedersenCommitmentLength)
return tecINTERNAL; // LCOV_EXCL_LINE
if (mpt_verify_convert_back_proof(
proof.data(),
pubKeySlice.data(),
spendingBalance.data(),
balanceCommitment.data(),
amount,
contextHash.data()) != 0)
return tecBAD_PROOF;
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -99,19 +99,36 @@ getBookBase(Book const& book)
{
XRPL_ASSERT(isConsistent(book), "xrpl::getBookBase : input is consistent");
auto const index = book.domain ? indexHash(
LedgerNameSpace::BOOK_DIR,
book.in.currency,
book.out.currency,
book.in.account,
book.out.account,
*(book.domain))
: indexHash(
LedgerNameSpace::BOOK_DIR,
book.in.currency,
book.out.currency,
book.in.account,
book.out.account);
auto getIndexHash = [&book]<typename... Args>(Args... args) {
if (book.domain)
return indexHash(std::forward<Args>(args)..., *book.domain);
return indexHash(std::forward<Args>(args)...);
};
auto const index = std::visit(
[&]<ValidIssueType TIn, ValidIssueType TOut>(TIn const& in, TOut const& out) {
if constexpr (std::is_same_v<TIn, Issue> && std::is_same_v<TOut, Issue>)
{
return getIndexHash(
LedgerNameSpace::BOOK_DIR, in.currency, out.currency, in.account, out.account);
}
else if constexpr (std::is_same_v<TIn, Issue> && std::is_same_v<TOut, MPTIssue>)
{
return getIndexHash(
LedgerNameSpace::BOOK_DIR, in.currency, out.getMptID(), in.account);
}
else if constexpr (std::is_same_v<TIn, MPTIssue> && std::is_same_v<TOut, Issue>)
{
return getIndexHash(
LedgerNameSpace::BOOK_DIR, in.getMptID(), out.currency, out.account);
}
else
{
return getIndexHash(LedgerNameSpace::BOOK_DIR, in.getMptID(), out.getMptID());
}
},
book.in.value(),
book.out.value());
// Return with quality 0.
auto k = keylet::quality({ltDIR_NODE, index}, 0);
@@ -401,11 +418,37 @@ nft_sells(uint256 const& id) noexcept
}
Keylet
amm(Asset const& issue1, Asset const& issue2) noexcept
amm(Asset const& asset1, Asset const& asset2) noexcept
{
auto const& [minI, maxI] = std::minmax(issue1.get<Issue>(), issue2.get<Issue>());
return amm(
indexHash(LedgerNameSpace::AMM, minI.account, minI.currency, maxI.account, maxI.currency));
auto const& [minA, maxA] = std::minmax(asset1, asset2);
return std::visit(
[]<ValidIssueType TIss1, ValidIssueType TIss2>(TIss1 const& issue1, TIss2 const& issue2) {
if constexpr (std::is_same_v<TIss1, Issue> && std::is_same_v<TIss2, Issue>)
{
return amm(indexHash(
LedgerNameSpace::AMM,
issue1.account,
issue1.currency,
issue2.account,
issue2.currency));
}
else if constexpr (std::is_same_v<TIss1, Issue> && std::is_same_v<TIss2, MPTIssue>)
{
return amm(indexHash(
LedgerNameSpace::AMM, issue1.account, issue1.currency, issue2.getMptID()));
}
else if constexpr (std::is_same_v<TIss1, MPTIssue> && std::is_same_v<TIss2, Issue>)
{
return amm(indexHash(
LedgerNameSpace::AMM, issue1.getMptID(), issue2.account, issue2.currency));
}
else if constexpr (std::is_same_v<TIss1, MPTIssue> && std::is_same_v<TIss2, MPTIssue>)
{
return amm(indexHash(LedgerNameSpace::AMM, issue1.getMptID(), issue2.getMptID()));
}
},
minA.value(),
maxA.value());
}
Keylet

View File

@@ -2,9 +2,8 @@
#include <xrpl/basics/contract.h>
#include <xrpl/json/json_errors.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
@@ -17,6 +16,11 @@ MPTIssue::MPTIssue(MPTID const& issuanceID) : mptID_(issuanceID)
{
}
MPTIssue::MPTIssue(std::uint32_t sequence, AccountID const& account)
: MPTIssue(xrpl::makeMptID(sequence, account))
{
}
AccountID const&
MPTIssue::getIssuer() const
{
@@ -86,4 +90,11 @@ mptIssueFromJson(Json::Value const& v)
return MPTIssue{id};
}
std::ostream&
operator<<(std::ostream& os, MPTIssue const& x)
{
os << to_string(x);
return os;
}
} // namespace xrpl

View File

@@ -0,0 +1,19 @@
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/PathAsset.h>
namespace xrpl {
std::string
to_string(PathAsset const& asset)
{
return std::visit([&](auto const& issue) { return to_string(issue); }, asset.value());
}
std::ostream&
operator<<(std::ostream& os, PathAsset const& x)
{
os << to_string(x);
return os;
}
} // namespace xrpl

View File

@@ -67,6 +67,11 @@ Permission::Permission()
#pragma pop_macro("PERMISSION")
};
XRPL_ASSERT(
txFeatureMap_.size() == delegableTx_.size(),
"xrpl::Permission : txFeatureMap_ and delegableTx_ must have same "
"size");
for ([[maybe_unused]] auto const& permission : granularPermissionMap_)
{
XRPL_ASSERT(

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