From 473f896133a0c86cbb1561b850d8377a0825faf6 Mon Sep 17 00:00:00 2001 From: bthomee Date: Mon, 2 Jun 2025 13:56:46 +0000 Subject: [PATCH] deploy: 621df422a79a6b24f0b35eac2ce719a660b0945f --- AMMBid_8cpp_source.html | 582 +- AMMBid_8h_source.html | 4 +- AMMCalc__test_8cpp_source.html | 6 +- AMMClawback_8cpp_source.html | 4 +- AMMClawback__test_8cpp_source.html | 2660 +-- AMMCore_8cpp_source.html | 2 +- AMMCore_8h_source.html | 2 +- AMMDeposit_8cpp_source.html | 608 +- AMMDeposit_8h_source.html | 12 +- AMMExtended__test_8cpp_source.html | 68 +- AMMHelpers_8cpp_source.html | 629 +- AMMHelpers_8h_source.html | 995 +- AMMInfo__test_8cpp_source.html | 353 +- AMMLiquidity_8cpp_source.html | 10 +- AMMLiquidity_8h_source.html | 2 +- AMMOffer_8cpp_source.html | 6 +- AMMTest_8cpp_source.html | 577 +- AMMTest_8h_source.html | 298 +- AMMWithdraw_8cpp_source.html | 699 +- AMMWithdraw_8h_source.html | 22 +- AMM_8cpp_source.html | 1726 +- AMM_8h_source.html | 579 +- AMM__test_8cpp_source.html | 15037 ++++++++-------- AccountDelete__test_8cpp_source.html | 2 +- AccountInfo__test_8cpp_source.html | 2 +- AccountLines__test_8cpp_source.html | 4 +- AccountObjects__test_8cpp_source.html | 2 +- AccountOffers__test_8cpp_source.html | 2 +- AccountTxPaging__test_8cpp_source.html | 2 +- AccountTx__test_8cpp_source.html | 2 +- AmendmentTable_8cpp_source.html | 2 +- AmendmentTable_8h_source.html | 2 +- AmendmentTable__test_8cpp_source.html | 2 +- AmountConversions_8h_source.html | 6 +- ApplyContext_8cpp_source.html | 2 +- ApplyViewBase_8cpp_source.html | 2 +- ApplyViewBase_8h_source.html | 2 +- Batch__test_8cpp_source.html | 2 +- BookChanges__test_8cpp_source.html | 2 +- BookStep_8cpp_source.html | 4 +- Book__test_8cpp_source.html | 2 +- CachedView_8h_source.html | 2 +- Check__test_8cpp_source.html | 2 +- ConsensusTypes_8h_source.html | 8 +- CreateOffer_8cpp_source.html | 8 +- CreateOffer_8h_source.html | 2 +- DeliveredAmount__test_8cpp_source.html | 2 +- DepositAuth__test_8cpp_source.html | 2 +- DepositAuthorized__test_8cpp_source.html | 2 +- DirectStep_8cpp_source.html | 4 +- Env_8cpp_source.html | 12 +- Env_8h_source.html | 313 +- Env__test_8cpp_source.html | 2 +- FeeVoteImpl_8cpp_source.html | 2 +- FeeVote_8h_source.html | 2 +- FixNFTokenPageLinks__test_8cpp_source.html | 2 +- IOUAmount_8cpp_source.html | 4 +- IOUAmount_8h_source.html | 225 +- IOUAmount__test_8cpp_source.html | 6 +- InvariantCheck_8cpp_source.html | 3670 ++-- InvariantCheck_8h_source.html | 233 +- JSONRPC__test_8cpp_source.html | 6 +- LPTokenTransfer__test_8cpp_source.html | 24 +- LedgerEntry__test_8cpp_source.html | 2 +- LedgerMaster_8cpp_source.html | 2 +- LedgerMaster_8h_source.html | 2 +- LedgerRPC__test_8cpp_source.html | 2 +- Ledger_8h_source.html | 2 +- MPToken__test_8cpp_source.html | 2 +- ManifestRPC__test_8cpp_source.html | 2 +- MultiSign__test_8cpp_source.html | 2 +- NFTokenBurn__test_8cpp_source.html | 2 +- NFTokenDir__test_8cpp_source.html | 2 +- NFTokenUtils_8cpp_source.html | 2 +- NFTokenUtils_8h_source.html | 2 +- NFToken__test_8cpp_source.html | 2 +- NetworkID__test_8cpp_source.html | 2 +- NoRipple__test_8cpp_source.html | 2 +- Number__test_8cpp_source.html | 2 +- OfferStream_8cpp_source.html | 2 +- OfferStream_8h_source.html | 2 +- Offer_8h_source.html | 617 +- Offer__test_8cpp_source.html | 2 +- OpenLedger_8cpp_source.html | 2 +- OpenLedger_8h_source.html | 2 +- OpenView_8cpp_source.html | 2 +- OpenView_8h_source.html | 2 +- Oracle_8cpp_source.html | 2 +- Oracle__test_8cpp_source.html | 2 +- PayChan__test_8cpp_source.html | 4 +- PaySteps_8cpp_source.html | 4 +- PseudoTx__test_8cpp_source.html | 2 +- ReadView_8cpp_source.html | 5 +- ReadView_8h_source.html | 2 +- ReducedOffer__test_8cpp_source.html | 2 +- Roles__test_8cpp_source.html | 2 +- Rules_8cpp_source.html | 15 +- Rules_8h_source.html | 177 +- SHAMapStore__test_8cpp_source.html | 2 +- STAmount_8cpp_source.html | 4 +- STAmount_8h_source.html | 2 +- STLedgerEntry_8cpp_source.html | 2 +- STLedgerEntry_8h_source.html | 2 +- STTx_8cpp_source.html | 2 +- STTx_8h_source.html | 2 +- STTx__test_8cpp_source.html | 2 +- ServerInfo__test_8cpp_source.html | 2 +- ServerStatus__test_8cpp_source.html | 2 +- SetSignerList_8cpp_source.html | 2 +- SetSignerList_8h_source.html | 2 +- SetTrust__test_8cpp_source.html | 2 +- Simulate__test_8cpp_source.html | 2 +- StrandFlow_8h_source.html | 2 +- Taker_8cpp_source.html | 8 +- Taker_8h_source.html | 2 +- Taker__test_8cpp_source.html | 2 +- TestHelpers_8cpp_source.html | 4 +- TestHelpers_8h_source.html | 2 +- TransactionSign_8cpp_source.html | 2 +- Transaction__ordering__test_8cpp_source.html | 2 +- Transactor_8cpp_source.html | 6 +- Transactor_8h_source.html | 2 +- TxQ_8cpp_source.html | 2 +- TxQ__test_8cpp_source.html | 2 +- ValidatorInfo__test_8cpp_source.html | 2 +- ValidatorRPC__test_8cpp_source.html | 2 +- View_8h_source.html | 8 +- annotated.html | 156 +- applySteps_8cpp_source.html | 2 +- applySteps_8h_source.html | 2 +- apply_8cpp_source.html | 4 +- classes.html | 4 +- classripple_1_1AMMBid.html | 4 +- classripple_1_1AMMDeposit.html | 12 +- classripple_1_1AMMWithdraw-members.html | 2 +- classripple_1_1AMMWithdraw.html | 25 +- classripple_1_1AccountRootsDeletedClean.html | 4 +- classripple_1_1AccountRootsNotDeleted.html | 4 +- ...ipple_1_1CurrentTransactionRulesGuard.html | 8 +- classripple_1_1IOUAmount-members.html | 7 +- classripple_1_1IOUAmount.html | 69 +- classripple_1_1LedgerEntryTypesMatch.html | 4 +- classripple_1_1NFTokenCountTracking.html | 4 +- classripple_1_1NoBadOffers.html | 4 +- ..._1NoDeepFreezeTrustLinesWithoutFreeze.html | 4 +- classripple_1_1NoXRPTrustLines.html | 4 +- classripple_1_1NoZeroEscrow.html | 4 +- classripple_1_1NumberSO.html | 8 +- classripple_1_1Rules.html | 4 +- classripple_1_1TOffer-members.html | 2 +- classripple_1_1TOffer.html | 74 +- classripple_1_1TOfferBase.html | 6 +- ...rBase_3_01STAmount_00_01STAmount_01_4.html | 2 +- classripple_1_1TransactionFeeCheck.html | 4 +- classripple_1_1TransfersNotFrozen.html | 18 +- classripple_1_1ValidAMM-members.html | 104 + classripple_1_1ValidAMM.html | 742 + classripple_1_1ValidAMM__coll__graph.map | 5 + classripple_1_1ValidAMM__coll__graph.md5 | 1 + classripple_1_1ValidAMM__coll__graph.png | Bin 0 -> 10207 bytes classripple_1_1ValidClawback.html | 4 +- classripple_1_1ValidMPTIssuance.html | 4 +- classripple_1_1ValidNFTokenPage.html | 4 +- classripple_1_1ValidNewAccountRoot.html | 4 +- classripple_1_1ValidPermissionedDEX.html | 4 +- classripple_1_1ValidPermissionedDomain.html | 4 +- classripple_1_1XRPBalanceChecks.html | 4 +- classripple_1_1XRPNotCreated.html | 4 +- ..._1_1test_1_1AMMClawback__test-members.html | 39 +- classripple_1_1test_1_1AMMClawback__test.html | 88 +- ...pple_1_1test_1_1AMMInfo__test-members.html | 27 +- classripple_1_1test_1_1AMMInfo__test.html | 79 +- ...test_1_1LPTokenTransfer__test-members.html | 29 +- ...pple_1_1test_1_1LPTokenTransfer__test.html | 72 +- ...sripple_1_1test_1_1jtx_1_1AMM-members.html | 48 +- classripple_1_1test_1_1jtx_1_1AMM.html | 205 +- ...ple_1_1test_1_1jtx_1_1AMMTest-members.html | 17 +- classripple_1_1test_1_1jtx_1_1AMMTest.html | 74 +- ...1_1test_1_1jtx_1_1AMMTestBase-members.html | 17 +- ...sripple_1_1test_1_1jtx_1_1AMMTestBase.html | 66 +- ...ple_1_1test_1_1jtx_1_1AMMTest_1_1gate.html | 12 +- ...sripple_1_1test_1_1jtx_1_1Env-members.html | 119 +- classripple_1_1test_1_1jtx_1_1Env.html | 56 +- compression__test_8cpp_source.html | 2 +- creds_8cpp_source.html | 2 +- delegate_8cpp_source.html | 2 +- functions_a.html | 31 +- functions_c.html | 38 +- functions_d.html | 2 +- functions_e.html | 6 +- functions_enum.html | 5 + functions_f.html | 27 +- functions_func.html | 2 +- functions_func_b.html | 8 +- functions_func_c.html | 6 +- functions_func_d.html | 2 +- functions_func_e.html | 18 +- functions_func_f.html | 35 +- functions_func_g.html | 5 +- functions_func_i.html | 2 + functions_func_j.html | 2 +- functions_func_m.html | 2 +- functions_func_n.html | 2 +- functions_func_p.html | 6 +- functions_func_r.html | 2 +- functions_func_s.html | 20 +- functions_func_t.html | 9 +- functions_func_v.html | 17 +- functions_g.html | 9 +- functions_h.html | 2 +- functions_i.html | 26 +- functions_k.html | 2 +- functions_l.html | 8 +- functions_m.html | 4 +- functions_n.html | 7 +- functions_o.html | 18 +- functions_p.html | 14 +- functions_q.html | 3 +- functions_r.html | 2 +- functions_rela_o.html | 6 +- functions_s.html | 44 +- functions_t.html | 35 +- functions_v.html | 13 +- functions_vars.html | 3 +- functions_vars_f.html | 2 +- functions_vars_l.html | 2 + functions_vars_n.html | 1 + functions_vars_p.html | 1 + functions_vars_t.html | 4 +- functions_w.html | 4 +- functions_z.html | 1 + hierarchy.html | 594 +- inherit_graph_1000.map | 3 +- inherit_graph_1000.md5 | 2 +- inherit_graph_1000.png | Bin 3774 -> 2276 bytes inherit_graph_1001.map | 6 +- inherit_graph_1001.md5 | 2 +- inherit_graph_1001.png | Bin 23274 -> 2177 bytes inherit_graph_1002.map | 4 +- inherit_graph_1002.md5 | 2 +- inherit_graph_1002.png | Bin 6025 -> 3774 bytes inherit_graph_1003.map | 7 +- inherit_graph_1003.md5 | 2 +- inherit_graph_1003.png | Bin 3746 -> 23274 bytes inherit_graph_1004.map | 5 +- inherit_graph_1004.md5 | 2 +- inherit_graph_1004.png | Bin 11164 -> 6025 bytes inherit_graph_1005.map | 4 +- inherit_graph_1005.md5 | 2 +- inherit_graph_1005.png | Bin 4571 -> 3746 bytes inherit_graph_1006.map | 5 +- inherit_graph_1006.md5 | 2 +- inherit_graph_1006.png | Bin 4347 -> 11164 bytes inherit_graph_1007.map | 20 +- inherit_graph_1007.md5 | 2 +- inherit_graph_1007.png | Bin 57980 -> 4571 bytes inherit_graph_1008.map | 26 +- inherit_graph_1008.md5 | 2 +- inherit_graph_1008.png | Bin 111738 -> 4347 bytes inherit_graph_1009.map | 22 +- inherit_graph_1009.md5 | 2 +- inherit_graph_1009.png | Bin 12078 -> 57980 bytes inherit_graph_1010.map | 27 +- inherit_graph_1010.md5 | 2 +- inherit_graph_1010.png | Bin 7487 -> 111738 bytes inherit_graph_1011.map | 6 +- inherit_graph_1011.md5 | 2 +- inherit_graph_1011.png | Bin 4332 -> 12078 bytes inherit_graph_1012.map | 5 +- inherit_graph_1012.md5 | 2 +- inherit_graph_1012.png | Bin 5454 -> 7487 bytes inherit_graph_1013.map | 12 +- inherit_graph_1013.md5 | 2 +- inherit_graph_1013.png | Bin 37543 -> 4332 bytes inherit_graph_1014.map | 5 +- inherit_graph_1014.md5 | 2 +- inherit_graph_1014.png | Bin 8135 -> 5454 bytes inherit_graph_1015.map | 24 +- inherit_graph_1015.md5 | 2 +- inherit_graph_1015.png | Bin 43699 -> 37543 bytes inherit_graph_1016.map | 9 +- inherit_graph_1016.md5 | 2 +- inherit_graph_1016.png | Bin 22241 -> 8135 bytes inherit_graph_1017.map | 16 +- inherit_graph_1017.md5 | 2 +- inherit_graph_1017.png | Bin 3127 -> 43699 bytes inherit_graph_1018.map | 7 +- inherit_graph_1018.md5 | 2 +- inherit_graph_1018.png | Bin 2348 -> 22241 bytes inherit_graph_1019.map | 5 +- inherit_graph_1019.md5 | 2 +- inherit_graph_1019.png | Bin 8451 -> 3127 bytes inherit_graph_1020.map | 3 +- inherit_graph_1020.md5 | 2 +- inherit_graph_1020.png | Bin 9534 -> 2348 bytes inherit_graph_1021.map | 5 +- inherit_graph_1021.md5 | 2 +- inherit_graph_1021.png | Bin 9125 -> 8451 bytes inherit_graph_1022.map | 4 +- inherit_graph_1022.md5 | 2 +- inherit_graph_1022.png | Bin 8391 -> 9534 bytes inherit_graph_1023.map | 4 +- inherit_graph_1023.md5 | 2 +- inherit_graph_1023.png | Bin 11456 -> 9125 bytes inherit_graph_1024.map | 4 +- inherit_graph_1024.md5 | 2 +- inherit_graph_1024.png | Bin 7948 -> 8391 bytes inherit_graph_1025.map | 4 +- inherit_graph_1025.md5 | 2 +- inherit_graph_1025.png | Bin 6714 -> 11456 bytes inherit_graph_1026.map | 8 +- inherit_graph_1026.md5 | 2 +- inherit_graph_1026.png | Bin 27916 -> 7948 bytes inherit_graph_1027.map | 8 +- inherit_graph_1027.md5 | 2 +- inherit_graph_1027.png | Bin 15693 -> 6714 bytes inherit_graph_1028.map | 7 +- inherit_graph_1028.md5 | 2 +- inherit_graph_1028.png | Bin 3207 -> 27916 bytes inherit_graph_1029.map | 8 +- inherit_graph_1029.md5 | 2 +- inherit_graph_1029.png | Bin 4581 -> 15693 bytes inherit_graph_1030.map | 3 +- inherit_graph_1030.md5 | 2 +- inherit_graph_1030.png | Bin 3834 -> 3207 bytes inherit_graph_1031.map | 22 +- inherit_graph_1031.md5 | 2 +- inherit_graph_1031.png | Bin 135659 -> 4581 bytes inherit_graph_1032.map | 4 +- inherit_graph_1032.md5 | 2 +- inherit_graph_1032.png | Bin 4173 -> 3834 bytes inherit_graph_1033.map | 36 +- inherit_graph_1033.md5 | 2 +- inherit_graph_1033.png | Bin 65131 -> 135659 bytes inherit_graph_1034.map | 4 +- inherit_graph_1034.md5 | 2 +- inherit_graph_1034.png | Bin 4970 -> 4173 bytes inherit_graph_1035.map | 19 +- inherit_graph_1035.md5 | 2 +- inherit_graph_1035.png | Bin 7463 -> 65131 bytes inherit_graph_1036.map | 4 + inherit_graph_1036.md5 | 1 + inherit_graph_1036.png | Bin 0 -> 4970 bytes inherit_graph_1037.map | 5 + inherit_graph_1037.md5 | 1 + inherit_graph_1037.png | Bin 0 -> 7463 bytes inherit_graph_856.map | 2 +- inherit_graph_856.md5 | 2 +- inherit_graph_856.png | Bin 1822 -> 2036 bytes inherit_graph_857.map | 2 +- inherit_graph_857.md5 | 2 +- inherit_graph_857.png | Bin 2169 -> 1822 bytes inherit_graph_858.map | 2 +- inherit_graph_858.md5 | 2 +- inherit_graph_858.png | Bin 1947 -> 2169 bytes inherit_graph_859.map | 2 +- inherit_graph_859.md5 | 2 +- inherit_graph_859.png | Bin 2162 -> 1947 bytes inherit_graph_860.map | 2 +- inherit_graph_860.md5 | 2 +- inherit_graph_860.png | Bin 2130 -> 2162 bytes inherit_graph_861.map | 2 +- inherit_graph_861.md5 | 2 +- inherit_graph_861.png | Bin 1944 -> 2130 bytes inherit_graph_862.map | 2 +- inherit_graph_862.md5 | 2 +- inherit_graph_862.png | Bin 2051 -> 1944 bytes inherit_graph_863.map | 2 +- inherit_graph_863.md5 | 2 +- inherit_graph_863.png | Bin 2045 -> 2051 bytes inherit_graph_864.map | 2 +- inherit_graph_864.md5 | 2 +- inherit_graph_864.png | Bin 1888 -> 2045 bytes inherit_graph_865.map | 2 +- inherit_graph_865.md5 | 2 +- inherit_graph_865.png | Bin 2008 -> 1888 bytes inherit_graph_866.map | 2 +- inherit_graph_866.md5 | 2 +- inherit_graph_866.png | Bin 1985 -> 2008 bytes inherit_graph_867.map | 2 +- inherit_graph_867.md5 | 2 +- inherit_graph_867.png | Bin 1800 -> 1985 bytes inherit_graph_868.map | 2 +- inherit_graph_868.md5 | 2 +- inherit_graph_868.png | Bin 2965 -> 1800 bytes inherit_graph_869.map | 2 +- inherit_graph_869.md5 | 2 +- inherit_graph_869.png | Bin 2598 -> 2965 bytes inherit_graph_870.map | 2 +- inherit_graph_870.md5 | 2 +- inherit_graph_870.png | Bin 2454 -> 2598 bytes inherit_graph_871.map | 2 +- inherit_graph_871.md5 | 2 +- inherit_graph_871.png | Bin 2627 -> 2454 bytes inherit_graph_872.map | 2 +- inherit_graph_872.md5 | 2 +- inherit_graph_872.png | Bin 2525 -> 2627 bytes inherit_graph_873.map | 2 +- inherit_graph_873.md5 | 2 +- inherit_graph_873.png | Bin 3063 -> 2525 bytes inherit_graph_874.map | 2 +- inherit_graph_874.md5 | 2 +- inherit_graph_874.png | Bin 2134 -> 3063 bytes inherit_graph_875.map | 2 +- inherit_graph_875.md5 | 2 +- inherit_graph_875.png | Bin 2703 -> 2134 bytes inherit_graph_876.map | 2 +- inherit_graph_876.md5 | 2 +- inherit_graph_876.png | Bin 1903 -> 2703 bytes inherit_graph_877.map | 2 +- inherit_graph_877.md5 | 2 +- inherit_graph_877.png | Bin 2430 -> 1903 bytes inherit_graph_878.map | 2 +- inherit_graph_878.md5 | 2 +- inherit_graph_878.png | Bin 1885 -> 2430 bytes inherit_graph_879.map | 2 +- inherit_graph_879.md5 | 2 +- inherit_graph_879.png | Bin 2458 -> 1885 bytes inherit_graph_880.map | 2 +- inherit_graph_880.md5 | 2 +- inherit_graph_880.png | Bin 1284 -> 2458 bytes inherit_graph_881.map | 2 +- inherit_graph_881.md5 | 2 +- inherit_graph_881.png | Bin 2130 -> 1284 bytes inherit_graph_882.map | 2 +- inherit_graph_882.md5 | 2 +- inherit_graph_882.png | Bin 1737 -> 2130 bytes inherit_graph_883.map | 2 +- inherit_graph_883.md5 | 2 +- inherit_graph_883.png | Bin 2037 -> 1737 bytes inherit_graph_884.map | 2 +- inherit_graph_884.md5 | 2 +- inherit_graph_884.png | Bin 2609 -> 2037 bytes inherit_graph_885.map | 2 +- inherit_graph_885.md5 | 2 +- inherit_graph_885.png | Bin 2149 -> 2609 bytes inherit_graph_886.map | 2 +- inherit_graph_886.md5 | 2 +- inherit_graph_886.png | Bin 2266 -> 2149 bytes inherit_graph_887.map | 2 +- inherit_graph_887.md5 | 2 +- inherit_graph_887.png | Bin 1381 -> 2266 bytes inherit_graph_888.map | 2 +- inherit_graph_888.md5 | 2 +- inherit_graph_888.png | Bin 1811 -> 1381 bytes inherit_graph_889.map | 2 +- inherit_graph_889.md5 | 2 +- inherit_graph_889.png | Bin 1639 -> 1811 bytes inherit_graph_890.map | 2 +- inherit_graph_890.md5 | 2 +- inherit_graph_890.png | Bin 2707 -> 1639 bytes inherit_graph_891.map | 2 +- inherit_graph_891.md5 | 2 +- inherit_graph_891.png | Bin 2336 -> 2707 bytes inherit_graph_892.map | 2 +- inherit_graph_892.md5 | 2 +- inherit_graph_892.png | Bin 2355 -> 2336 bytes inherit_graph_893.map | 2 +- inherit_graph_893.md5 | 2 +- inherit_graph_893.png | Bin 1946 -> 2355 bytes inherit_graph_894.map | 2 +- inherit_graph_894.md5 | 2 +- inherit_graph_894.png | Bin 2103 -> 1946 bytes inherit_graph_895.map | 2 +- inherit_graph_895.md5 | 2 +- inherit_graph_895.png | Bin 2089 -> 2103 bytes inherit_graph_896.map | 2 +- inherit_graph_896.md5 | 2 +- inherit_graph_896.png | Bin 1879 -> 2089 bytes inherit_graph_897.map | 2 +- inherit_graph_897.md5 | 2 +- inherit_graph_897.png | Bin 2044 -> 1879 bytes inherit_graph_898.map | 2 +- inherit_graph_898.md5 | 2 +- inherit_graph_898.png | Bin 2160 -> 2044 bytes inherit_graph_899.map | 2 +- inherit_graph_899.md5 | 2 +- inherit_graph_899.png | Bin 2566 -> 2160 bytes inherit_graph_900.map | 2 +- inherit_graph_900.md5 | 2 +- inherit_graph_900.png | Bin 2751 -> 2566 bytes inherit_graph_901.map | 2 +- inherit_graph_901.md5 | 2 +- inherit_graph_901.png | Bin 2595 -> 2751 bytes inherit_graph_902.map | 2 +- inherit_graph_902.md5 | 2 +- inherit_graph_902.png | Bin 2832 -> 2595 bytes inherit_graph_903.map | 2 +- inherit_graph_903.md5 | 2 +- inherit_graph_903.png | Bin 1760 -> 2832 bytes inherit_graph_904.map | 2 +- inherit_graph_904.md5 | 2 +- inherit_graph_904.png | Bin 2016 -> 1760 bytes inherit_graph_905.map | 2 +- inherit_graph_905.md5 | 2 +- inherit_graph_905.png | Bin 3019 -> 2016 bytes inherit_graph_906.map | 2 +- inherit_graph_906.md5 | 2 +- inherit_graph_906.png | Bin 2286 -> 3019 bytes inherit_graph_907.map | 2 +- inherit_graph_907.md5 | 2 +- inherit_graph_907.png | Bin 3095 -> 2286 bytes inherit_graph_908.map | 2 +- inherit_graph_908.md5 | 2 +- inherit_graph_908.png | Bin 3298 -> 3095 bytes inherit_graph_909.map | 2 +- inherit_graph_909.md5 | 2 +- inherit_graph_909.png | Bin 2735 -> 3298 bytes inherit_graph_910.map | 2 +- inherit_graph_910.md5 | 2 +- inherit_graph_910.png | Bin 3823 -> 2735 bytes inherit_graph_911.map | 2 +- inherit_graph_911.md5 | 2 +- inherit_graph_911.png | Bin 3116 -> 3823 bytes inherit_graph_912.map | 2 +- inherit_graph_912.md5 | 2 +- inherit_graph_912.png | Bin 2755 -> 3116 bytes inherit_graph_913.map | 3 +- inherit_graph_913.md5 | 2 +- inherit_graph_913.png | Bin 4619 -> 2755 bytes inherit_graph_914.map | 4 +- inherit_graph_914.md5 | 2 +- inherit_graph_914.png | Bin 4008 -> 4619 bytes inherit_graph_915.map | 3 +- inherit_graph_915.md5 | 2 +- inherit_graph_915.png | Bin 3217 -> 4008 bytes inherit_graph_916.map | 2 +- inherit_graph_916.md5 | 2 +- inherit_graph_916.png | Bin 2672 -> 3217 bytes inherit_graph_917.map | 2 +- inherit_graph_917.md5 | 2 +- inherit_graph_917.png | Bin 2655 -> 2672 bytes inherit_graph_918.map | 2 +- inherit_graph_918.md5 | 2 +- inherit_graph_918.png | Bin 1432 -> 2655 bytes inherit_graph_919.map | 2 +- inherit_graph_919.md5 | 2 +- inherit_graph_919.png | Bin 2201 -> 1432 bytes inherit_graph_920.map | 2 +- inherit_graph_920.md5 | 2 +- inherit_graph_920.png | Bin 2507 -> 2201 bytes inherit_graph_921.map | 2 +- inherit_graph_921.md5 | 2 +- inherit_graph_921.png | Bin 3156 -> 2507 bytes inherit_graph_922.map | 2 +- inherit_graph_922.md5 | 2 +- inherit_graph_923.map | 5 +- inherit_graph_923.md5 | 2 +- inherit_graph_923.png | Bin 10427 -> 3156 bytes inherit_graph_924.map | 6 +- inherit_graph_924.md5 | 2 +- inherit_graph_924.png | Bin 4720 -> 10427 bytes inherit_graph_925.map | 4 +- inherit_graph_925.md5 | 2 +- inherit_graph_925.png | Bin 4177 -> 4720 bytes inherit_graph_926.map | 3 +- inherit_graph_926.md5 | 2 +- inherit_graph_926.png | Bin 3446 -> 4177 bytes inherit_graph_927.map | 2 +- inherit_graph_927.md5 | 2 +- inherit_graph_927.png | Bin 1868 -> 3446 bytes inherit_graph_928.map | 2 +- inherit_graph_928.md5 | 2 +- inherit_graph_928.png | Bin 2505 -> 1868 bytes inherit_graph_929.map | 2 +- inherit_graph_929.md5 | 2 +- inherit_graph_929.png | Bin 2804 -> 2505 bytes inherit_graph_930.map | 2 +- inherit_graph_930.md5 | 2 +- inherit_graph_930.png | Bin 1957 -> 2804 bytes inherit_graph_931.map | 2 +- inherit_graph_931.md5 | 2 +- inherit_graph_931.png | Bin 2588 -> 1957 bytes inherit_graph_932.map | 2 +- inherit_graph_932.md5 | 2 +- inherit_graph_932.png | Bin 2058 -> 2588 bytes inherit_graph_933.map | 2 +- inherit_graph_933.md5 | 2 +- inherit_graph_933.png | Bin 1880 -> 2058 bytes inherit_graph_934.map | 66 +- inherit_graph_934.md5 | 2 +- inherit_graph_934.png | Bin 421177 -> 1880 bytes inherit_graph_935.map | 66 +- inherit_graph_935.md5 | 2 +- inherit_graph_935.png | Bin 1925 -> 421177 bytes inherit_graph_936.map | 2 +- inherit_graph_936.md5 | 2 +- inherit_graph_936.png | Bin 2932 -> 1925 bytes inherit_graph_937.map | 2 +- inherit_graph_937.md5 | 2 +- inherit_graph_937.png | Bin 2794 -> 2932 bytes inherit_graph_938.map | 2 +- inherit_graph_938.md5 | 2 +- inherit_graph_938.png | Bin 1853 -> 2794 bytes inherit_graph_939.map | 2 +- inherit_graph_939.md5 | 2 +- inherit_graph_939.png | Bin 1693 -> 1853 bytes inherit_graph_940.map | 2 +- inherit_graph_940.md5 | 2 +- inherit_graph_940.png | Bin 2596 -> 1693 bytes inherit_graph_941.map | 2 +- inherit_graph_941.md5 | 2 +- inherit_graph_941.png | Bin 1493 -> 2596 bytes inherit_graph_942.map | 2 +- inherit_graph_942.md5 | 2 +- inherit_graph_942.png | Bin 1988 -> 1493 bytes inherit_graph_943.map | 2 +- inherit_graph_943.md5 | 2 +- inherit_graph_943.png | Bin 1370 -> 1988 bytes inherit_graph_944.map | 2 +- inherit_graph_944.md5 | 2 +- inherit_graph_944.png | Bin 1938 -> 1370 bytes inherit_graph_945.map | 2 +- inherit_graph_945.md5 | 2 +- inherit_graph_945.png | Bin 1263 -> 1938 bytes inherit_graph_946.map | 2 +- inherit_graph_946.md5 | 2 +- inherit_graph_946.png | Bin 2018 -> 1263 bytes inherit_graph_947.map | 2 +- inherit_graph_947.md5 | 2 +- inherit_graph_947.png | Bin 1903 -> 2018 bytes inherit_graph_948.map | 2 +- inherit_graph_948.md5 | 2 +- inherit_graph_948.png | Bin 2751 -> 1903 bytes inherit_graph_949.map | 2 +- inherit_graph_949.md5 | 2 +- inherit_graph_949.png | Bin 1844 -> 2751 bytes inherit_graph_950.map | 2 +- inherit_graph_950.md5 | 2 +- inherit_graph_950.png | Bin 1834 -> 1844 bytes inherit_graph_951.map | 2 +- inherit_graph_951.md5 | 2 +- inherit_graph_951.png | Bin 2272 -> 1834 bytes inherit_graph_952.map | 2 +- inherit_graph_952.md5 | 2 +- inherit_graph_952.png | Bin 1668 -> 2272 bytes inherit_graph_953.map | 2 +- inherit_graph_953.md5 | 2 +- inherit_graph_953.png | Bin 1877 -> 1668 bytes inherit_graph_954.map | 2 +- inherit_graph_954.md5 | 2 +- inherit_graph_954.png | Bin 1986 -> 1877 bytes inherit_graph_955.map | 2 +- inherit_graph_955.md5 | 2 +- inherit_graph_955.png | Bin 1463 -> 1986 bytes inherit_graph_956.map | 3 +- inherit_graph_956.md5 | 2 +- inherit_graph_956.png | Bin 4824 -> 1463 bytes inherit_graph_957.map | 3 +- inherit_graph_957.md5 | 2 +- inherit_graph_957.png | Bin 1978 -> 4824 bytes inherit_graph_958.map | 2 +- inherit_graph_958.md5 | 2 +- inherit_graph_958.png | Bin 2648 -> 1978 bytes inherit_graph_959.map | 2 +- inherit_graph_959.md5 | 2 +- inherit_graph_959.png | Bin 1783 -> 2648 bytes inherit_graph_960.map | 2 +- inherit_graph_960.md5 | 2 +- inherit_graph_960.png | Bin 2036 -> 1783 bytes inherit_graph_961.map | 2 +- inherit_graph_961.md5 | 2 +- inherit_graph_961.png | Bin 2647 -> 1604 bytes inherit_graph_962.map | 2 +- inherit_graph_962.md5 | 2 +- inherit_graph_962.png | Bin 3359 -> 2036 bytes inherit_graph_963.map | 2 +- inherit_graph_963.md5 | 2 +- inherit_graph_963.png | Bin 3178 -> 2647 bytes inherit_graph_964.map | 2 +- inherit_graph_964.md5 | 2 +- inherit_graph_964.png | Bin 3240 -> 3359 bytes inherit_graph_965.map | 2 +- inherit_graph_965.md5 | 2 +- inherit_graph_965.png | Bin 1979 -> 3178 bytes inherit_graph_966.map | 2 +- inherit_graph_966.md5 | 2 +- inherit_graph_966.png | Bin 2045 -> 3240 bytes inherit_graph_967.map | 2 +- inherit_graph_967.md5 | 2 +- inherit_graph_967.png | Bin 2332 -> 1979 bytes inherit_graph_968.map | 2 +- inherit_graph_968.md5 | 2 +- inherit_graph_968.png | Bin 1760 -> 2045 bytes inherit_graph_969.map | 2 +- inherit_graph_969.md5 | 2 +- inherit_graph_969.png | Bin 2869 -> 2332 bytes inherit_graph_970.map | 2 +- inherit_graph_970.md5 | 2 +- inherit_graph_970.png | Bin 2289 -> 1760 bytes inherit_graph_971.map | 2 +- inherit_graph_971.md5 | 2 +- inherit_graph_971.png | Bin 2831 -> 2869 bytes inherit_graph_972.map | 2 +- inherit_graph_972.md5 | 2 +- inherit_graph_972.png | Bin 2526 -> 2289 bytes inherit_graph_973.map | 2 +- inherit_graph_973.md5 | 2 +- inherit_graph_973.png | Bin 1818 -> 2831 bytes inherit_graph_974.map | 2 +- inherit_graph_974.md5 | 2 +- inherit_graph_974.png | Bin 2014 -> 2526 bytes inherit_graph_975.map | 2 +- inherit_graph_975.md5 | 2 +- inherit_graph_975.png | Bin 2528 -> 1818 bytes inherit_graph_976.map | 2 +- inherit_graph_976.md5 | 2 +- inherit_graph_976.png | Bin 2402 -> 2014 bytes inherit_graph_977.map | 2 +- inherit_graph_977.md5 | 2 +- inherit_graph_977.png | Bin 1844 -> 2528 bytes inherit_graph_978.map | 2 +- inherit_graph_978.md5 | 2 +- inherit_graph_978.png | Bin 2009 -> 2402 bytes inherit_graph_979.map | 2 +- inherit_graph_979.md5 | 2 +- inherit_graph_979.png | Bin 2083 -> 1844 bytes inherit_graph_980.map | 2 +- inherit_graph_980.md5 | 2 +- inherit_graph_980.png | Bin 2575 -> 2009 bytes inherit_graph_981.map | 2 +- inherit_graph_981.md5 | 2 +- inherit_graph_981.png | Bin 2193 -> 2083 bytes inherit_graph_982.map | 2 +- inherit_graph_982.md5 | 2 +- inherit_graph_982.png | Bin 2416 -> 2575 bytes inherit_graph_983.map | 2 +- inherit_graph_983.md5 | 2 +- inherit_graph_983.png | Bin 2465 -> 2193 bytes inherit_graph_984.map | 2 +- inherit_graph_984.md5 | 2 +- inherit_graph_984.png | Bin 3109 -> 2416 bytes inherit_graph_985.map | 2 +- inherit_graph_985.md5 | 2 +- inherit_graph_985.png | Bin 2382 -> 2465 bytes inherit_graph_986.map | 2 +- inherit_graph_986.md5 | 2 +- inherit_graph_986.png | Bin 1543 -> 3109 bytes inherit_graph_987.map | 4 +- inherit_graph_987.md5 | 2 +- inherit_graph_987.png | Bin 6535 -> 2382 bytes inherit_graph_988.map | 2 +- inherit_graph_988.md5 | 2 +- inherit_graph_988.png | Bin 2335 -> 1543 bytes inherit_graph_989.map | 5 +- inherit_graph_989.md5 | 2 +- inherit_graph_989.png | Bin 2157 -> 6535 bytes inherit_graph_990.map | 3 +- inherit_graph_990.md5 | 2 +- inherit_graph_990.png | Bin 4529 -> 2335 bytes inherit_graph_991.map | 3 +- inherit_graph_991.md5 | 2 +- inherit_graph_991.png | Bin 3142 -> 2157 bytes inherit_graph_992.map | 4 +- inherit_graph_992.md5 | 2 +- inherit_graph_992.png | Bin 5351 -> 4529 bytes inherit_graph_993.map | 3 +- inherit_graph_993.md5 | 2 +- inherit_graph_993.png | Bin 4492 -> 3142 bytes inherit_graph_994.map | 3 +- inherit_graph_994.md5 | 2 +- inherit_graph_994.png | Bin 2254 -> 5351 bytes inherit_graph_995.map | 3 +- inherit_graph_995.md5 | 2 +- inherit_graph_995.png | Bin 3158 -> 4492 bytes inherit_graph_996.map | 2 +- inherit_graph_996.md5 | 2 +- inherit_graph_996.png | Bin 2686 -> 2254 bytes inherit_graph_997.map | 2 +- inherit_graph_997.md5 | 2 +- inherit_graph_997.png | Bin 3316 -> 3158 bytes inherit_graph_998.map | 2 +- inherit_graph_998.md5 | 2 +- inherit_graph_998.png | Bin 2276 -> 2686 bytes inherit_graph_999.map | 2 +- inherit_graph_999.md5 | 2 +- inherit_graph_999.png | Bin 2177 -> 3316 bytes inherits.html | 370 +- menudata.js | 3 +- namespacemembers.html | 18 +- namespacemembers_e.html | 2 +- namespacemembers_enum.html | 1 + namespacemembers_f.html | 2 +- namespacemembers_func.html | 16 +- namespacemembers_func_g.html | 6 +- namespacemembers_func_i.html | 6 +- namespacemembers_func_l.html | 4 +- namespacemembers_func_m.html | 7 +- namespacemembers_func_v.html | 3 +- namespacemembers_func_w.html | 1 - namespacemembers_g.html | 8 +- namespacemembers_i.html | 15 +- namespacemembers_l.html | 4 +- namespacemembers_t.html | 2 +- namespacemembers_type_i.html | 2 +- namespacemembers_v.html | 1 + namespacemembers_w.html | 1 - namespaceripple.html | 737 +- namespaceripple_1_1detail.html | 44 + namespaceripple_1_1test_1_1jtx.html | 50 +- namespaceripple_1_1test_1_1jtx_1_1amm.html | 6 +- namespaces.html | 164 +- permissioned__domains_8cpp_source.html | 2 +- search/all_10.js | 843 +- search/all_11.js | 693 +- search/all_12.js | 19 +- search/all_13.js | 1142 +- search/all_14.js | 2570 +-- search/all_15.js | 3332 ++-- search/all_16.js | 638 +- search/all_17.js | 543 +- search/all_18.js | 142 +- search/all_1a.js | 21 +- search/all_1b.js | 17 +- search/all_1c.js | 8 +- search/all_2.js | 1323 +- search/all_3.js | 370 +- search/all_4.js | 1930 +- search/all_5.js | 287 +- search/all_6.js | 656 +- search/all_7.js | 618 +- search/all_8.js | 1375 +- search/all_9.js | 359 +- search/all_a.js | 1712 +- search/all_b.js | 198 +- search/all_c.js | 8 +- search/all_d.js | 791 +- search/all_e.js | 750 +- search/all_f.js | 225 +- search/classes_13.js | 257 +- search/classes_15.js | 284 +- search/classes_e.js | 89 +- search/enums_16.js | 4 + search/enums_8.js | 5 +- search/enumvalues_1.js | 25 +- search/enumvalues_19.js | 6 +- search/enumvalues_2.js | 4 +- search/enumvalues_3.js | 4 +- search/enumvalues_5.js | 4 +- search/enumvalues_6.js | 5 +- search/enumvalues_9.js | 6 +- search/enumvalues_e.js | 67 +- search/enumvalues_f.js | 6 +- search/functions_1.js | 914 +- search/functions_10.js | 551 +- search/functions_12.js | 247 +- search/functions_13.js | 1257 +- search/functions_14.js | 2092 +-- search/functions_15.js | 209 +- search/functions_16.js | 227 +- search/functions_17.js | 135 +- search/functions_1b.js | 8 +- search/functions_2.js | 310 +- search/functions_3.js | 526 +- search/functions_4.js | 8 +- search/functions_5.js | 16 +- search/functions_6.js | 486 +- search/functions_7.js | 1245 +- search/functions_9.js | 695 +- search/functions_a.js | 6 +- search/functions_b.js | 37 +- search/functions_c.js | 4 +- search/functions_d.js | 367 +- search/functions_e.js | 208 +- search/functions_f.js | 61 +- search/related_9.js | 4 +- search/searchdata.js | 2 +- search/typedefs_8.js | 57 +- search/typedefs_c.js | 4 +- search/variables_0.js | 159 +- search/variables_13.js | 10 +- search/variables_16.js | 6 +- search/variables_3.js | 4 +- search/variables_5.js | 10 +- search/variables_b.js | 32 +- search/variables_c.js | 4 +- search/variables_d.js | 81 +- search/variables_f.js | 153 +- ..._1_1test_1_1AMMExtended__test-members.html | 115 +- ...ctripple_1_1test_1_1AMMExtended__test.html | 72 +- ...ctripple_1_1test_1_1AMM__test-members.html | 51 +- structripple_1_1test_1_1AMM__test.html | 296 +- ..._1_1test_1_1jtx_1_1TestAMMArg-members.html | 93 + ...ctripple_1_1test_1_1jtx_1_1TestAMMArg.html | 202 + ...test_1_1jtx_1_1TestAMMArg__coll__graph.map | 9 + ...test_1_1jtx_1_1TestAMMArg__coll__graph.md5 | 1 + ...test_1_1jtx_1_1TestAMMArg__coll__graph.png | Bin 0 -> 25027 bytes 887 files changed, 36630 insertions(+), 32082 deletions(-) create mode 100644 classripple_1_1ValidAMM-members.html create mode 100644 classripple_1_1ValidAMM.html create mode 100644 classripple_1_1ValidAMM__coll__graph.map create mode 100644 classripple_1_1ValidAMM__coll__graph.md5 create mode 100644 classripple_1_1ValidAMM__coll__graph.png create mode 100644 inherit_graph_1036.map create mode 100644 inherit_graph_1036.md5 create mode 100644 inherit_graph_1036.png create mode 100644 inherit_graph_1037.map create mode 100644 inherit_graph_1037.md5 create mode 100644 inherit_graph_1037.png create mode 100644 search/enums_16.js create mode 100644 structripple_1_1test_1_1jtx_1_1TestAMMArg-members.html create mode 100644 structripple_1_1test_1_1jtx_1_1TestAMMArg.html create mode 100644 structripple_1_1test_1_1jtx_1_1TestAMMArg__coll__graph.map create mode 100644 structripple_1_1test_1_1jtx_1_1TestAMMArg__coll__graph.md5 create mode 100644 structripple_1_1test_1_1jtx_1_1TestAMMArg__coll__graph.png diff --git a/AMMBid_8cpp_source.html b/AMMBid_8cpp_source.html index 8eebe40c33..90aff5c370 100644 --- a/AMMBid_8cpp_source.html +++ b/AMMBid_8cpp_source.html @@ -156,302 +156,319 @@ $(function() {
78 JLOG(ctx.j.debug()) << "AMM Bid: Invalid number of AuthAccounts.";
79 return temMALFORMED;
80 }
-
81 }
-
82
-
83 return preflight2(ctx);
-
84}
-
85
-
86TER
-
87AMMBid::preclaim(PreclaimContext const& ctx)
-
88{
-
89 auto const ammSle =
-
90 ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
-
91 if (!ammSle)
-
92 {
-
93 JLOG(ctx.j.debug()) << "AMM Bid: Invalid asset pair.";
-
94 return terNO_AMM;
-
95 }
-
96
-
97 auto const lpTokensBalance = (*ammSle)[sfLPTokenBalance];
-
98 if (lpTokensBalance == beast::zero)
-
99 return tecAMM_EMPTY;
+
81 else if (ctx.rules.enabled(fixAMMv1_3))
+
82 {
+
83 AccountID account = ctx.tx[sfAccount];
+
84 std::set<AccountID> unique;
+
85 for (auto const& obj : authAccounts)
+
86 {
+
87 auto authAccount = obj[sfAccount];
+
88 if (authAccount == account || unique.contains(authAccount))
+
89 {
+
90 JLOG(ctx.j.debug()) << "AMM Bid: Invalid auth.account.";
+
91 return temMALFORMED;
+
92 }
+
93 unique.insert(authAccount);
+
94 }
+
95 }
+
96 }
+
97
+
98 return preflight2(ctx);
+
99}
100
-
101 if (ctx.tx.isFieldPresent(sfAuthAccounts))
-
102 {
-
103 for (auto const& account : ctx.tx.getFieldArray(sfAuthAccounts))
-
104 {
-
105 if (!ctx.view.read(keylet::account(account[sfAccount])))
-
106 {
-
107 JLOG(ctx.j.debug()) << "AMM Bid: Invalid Account.";
-
108 return terNO_ACCOUNT;
-
109 }
-
110 }
-
111 }
-
112
-
113 auto const lpTokens =
-
114 ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
-
115 // Not LP
-
116 if (lpTokens == beast::zero)
+
101TER
+
102AMMBid::preclaim(PreclaimContext const& ctx)
+
103{
+
104 auto const ammSle =
+
105 ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
+
106 if (!ammSle)
+
107 {
+
108 JLOG(ctx.j.debug()) << "AMM Bid: Invalid asset pair.";
+
109 return terNO_AMM;
+
110 }
+
111
+
112 auto const lpTokensBalance = (*ammSle)[sfLPTokenBalance];
+
113 if (lpTokensBalance == beast::zero)
+
114 return tecAMM_EMPTY;
+
115
+
116 if (ctx.tx.isFieldPresent(sfAuthAccounts))
117 {
-
118 JLOG(ctx.j.debug()) << "AMM Bid: account is not LP.";
-
119 return tecAMM_INVALID_TOKENS;
-
120 }
-
121
-
122 auto const bidMin = ctx.tx[~sfBidMin];
-
123
-
124 if (bidMin)
-
125 {
-
126 if (bidMin->issue() != lpTokens.issue())
-
127 {
-
128 JLOG(ctx.j.debug()) << "AMM Bid: Invalid LPToken.";
-
129 return temBAD_AMM_TOKENS;
-
130 }
-
131 if (*bidMin > lpTokens || *bidMin >= lpTokensBalance)
-
132 {
-
133 JLOG(ctx.j.debug()) << "AMM Bid: Invalid Tokens.";
-
134 return tecAMM_INVALID_TOKENS;
-
135 }
-
136 }
-
137
-
138 auto const bidMax = ctx.tx[~sfBidMax];
-
139 if (bidMax)
+
118 for (auto const& account : ctx.tx.getFieldArray(sfAuthAccounts))
+
119 {
+
120 if (!ctx.view.read(keylet::account(account[sfAccount])))
+
121 {
+
122 JLOG(ctx.j.debug()) << "AMM Bid: Invalid Account.";
+
123 return terNO_ACCOUNT;
+
124 }
+
125 }
+
126 }
+
127
+
128 auto const lpTokens =
+
129 ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
+
130 // Not LP
+
131 if (lpTokens == beast::zero)
+
132 {
+
133 JLOG(ctx.j.debug()) << "AMM Bid: account is not LP.";
+
134 return tecAMM_INVALID_TOKENS;
+
135 }
+
136
+
137 auto const bidMin = ctx.tx[~sfBidMin];
+
138
+
139 if (bidMin)
140 {
-
141 if (bidMax->issue() != lpTokens.issue())
+
141 if (bidMin->issue() != lpTokens.issue())
142 {
143 JLOG(ctx.j.debug()) << "AMM Bid: Invalid LPToken.";
144 return temBAD_AMM_TOKENS;
145 }
-
146 if (*bidMax > lpTokens || *bidMax >= lpTokensBalance)
+
146 if (*bidMin > lpTokens || *bidMin >= lpTokensBalance)
147 {
148 JLOG(ctx.j.debug()) << "AMM Bid: Invalid Tokens.";
149 return tecAMM_INVALID_TOKENS;
150 }
151 }
152
-
153 if (bidMin && bidMax && bidMin > bidMax)
-
154 {
-
155 JLOG(ctx.j.debug()) << "AMM Bid: Invalid Max/MinSlotPrice.";
-
156 return tecAMM_INVALID_TOKENS;
-
157 }
-
158
-
159 return tesSUCCESS;
-
160}
-
161
-
162static std::pair<TER, bool>
-
163applyBid(
-
164 ApplyContext& ctx_,
-
165 Sandbox& sb,
-
166 AccountID const& account_,
-
167 beast::Journal j_)
-
168{
-
169 using namespace std::chrono;
-
170 auto const ammSle =
-
171 sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
-
172 if (!ammSle)
-
173 return {tecINTERNAL, false};
-
174 STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
-
175 auto const lpTokens = ammLPHolds(sb, *ammSle, account_, ctx_.journal);
-
176 auto const& rules = ctx_.view().rules();
-
177 if (!rules.enabled(fixInnerObjTemplate))
-
178 {
-
179 if (!ammSle->isFieldPresent(sfAuctionSlot))
-
180 ammSle->makeFieldPresent(sfAuctionSlot);
-
181 }
-
182 else
-
183 {
-
184 XRPL_ASSERT(
-
185 ammSle->isFieldPresent(sfAuctionSlot),
-
186 "ripple::applyBid : has auction slot");
-
187 if (!ammSle->isFieldPresent(sfAuctionSlot))
-
188 return {tecINTERNAL, false};
-
189 }
-
190 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
-
191 auto const current =
-
192 duration_cast<seconds>(
-
193 ctx_.view().info().parentCloseTime.time_since_epoch())
-
194 .count();
-
195 // Auction slot discounted fee
-
196 auto const discountedFee =
-
197 (*ammSle)[sfTradingFee] / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION;
-
198 auto const tradingFee = getFee((*ammSle)[sfTradingFee]);
-
199 // Min price
-
200 auto const minSlotPrice =
-
201 lptAMMBalance * tradingFee / AUCTION_SLOT_MIN_FEE_FRACTION;
-
202
-
203 std::uint32_t constexpr tailingSlot = AUCTION_SLOT_TIME_INTERVALS - 1;
-
204
-
205 // If seated then it is the current slot-holder time slot, otherwise
-
206 // the auction slot is not owned. Slot range is in {0-19}
-
207 auto const timeSlot = ammAuctionTimeSlot(current, auctionSlot);
-
208
-
209 // Account must exist and the slot not expired.
-
210 auto validOwner = [&](AccountID const& account) {
-
211 // Valid range is 0-19 but the tailing slot pays MinSlotPrice
-
212 // and doesn't refund so the check is < instead of <= to optimize.
-
213 return timeSlot && *timeSlot < tailingSlot &&
-
214 sb.read(keylet::account(account));
-
215 };
-
216
-
217 auto updateSlot = [&](std::uint32_t fee,
-
218 Number const& minPrice,
-
219 Number const& burn) -> TER {
-
220 auctionSlot.setAccountID(sfAccount, account_);
-
221 auctionSlot.setFieldU32(sfExpiration, current + TOTAL_TIME_SLOT_SECS);
-
222 if (fee != 0)
-
223 auctionSlot.setFieldU16(sfDiscountedFee, fee);
-
224 else if (auctionSlot.isFieldPresent(sfDiscountedFee))
-
225 auctionSlot.makeFieldAbsent(sfDiscountedFee);
-
226 auctionSlot.setFieldAmount(
-
227 sfPrice, toSTAmount(lpTokens.issue(), minPrice));
-
228 if (ctx_.tx.isFieldPresent(sfAuthAccounts))
-
229 auctionSlot.setFieldArray(
-
230 sfAuthAccounts, ctx_.tx.getFieldArray(sfAuthAccounts));
-
231 else
-
232 auctionSlot.makeFieldAbsent(sfAuthAccounts);
-
233 // Burn the remaining bid amount
-
234 auto const saBurn = adjustLPTokens(
-
235 lptAMMBalance, toSTAmount(lptAMMBalance.issue(), burn), false);
-
236 if (saBurn >= lptAMMBalance)
-
237 {
-
238 // This error case should never occur.
-
239 JLOG(ctx_.journal.fatal())
-
240 << "AMM Bid: LP Token burn exceeds AMM balance " << burn << " "
-
241 << lptAMMBalance;
-
242 return tecINTERNAL;
-
243 }
-
244 auto res =
-
245 redeemIOU(sb, account_, saBurn, lpTokens.issue(), ctx_.journal);
-
246 if (res != tesSUCCESS)
-
247 {
-
248 JLOG(ctx_.journal.debug()) << "AMM Bid: failed to redeem.";
-
249 return res;
-
250 }
-
251 ammSle->setFieldAmount(sfLPTokenBalance, lptAMMBalance - saBurn);
-
252 sb.update(ammSle);
-
253 return tesSUCCESS;
-
254 };
-
255
-
256 TER res = tesSUCCESS;
-
257
-
258 auto const bidMin = ctx_.tx[~sfBidMin];
-
259 auto const bidMax = ctx_.tx[~sfBidMax];
-
260
-
261 auto getPayPrice =
-
262 [&](Number const& computedPrice) -> Expected<Number, TER> {
-
263 auto const payPrice = [&]() -> std::optional<Number> {
-
264 // Both min/max bid price are defined
-
265 if (bidMin && bidMax)
-
266 {
-
267 if (computedPrice <= *bidMax)
-
268 return std::max(computedPrice, Number(*bidMin));
-
269 JLOG(ctx_.journal.debug())
-
270 << "AMM Bid: not in range " << computedPrice << " "
-
271 << *bidMin << " " << *bidMax;
-
272 return std::nullopt;
-
273 }
-
274 // Bidder pays max(bidPrice, computedPrice)
-
275 if (bidMin)
-
276 {
-
277 return std::max(computedPrice, Number(*bidMin));
-
278 }
-
279 else if (bidMax)
-
280 {
-
281 if (computedPrice <= *bidMax)
-
282 return computedPrice;
-
283 JLOG(ctx_.journal.debug()) << "AMM Bid: not in range "
-
284 << computedPrice << " " << *bidMax;
-
285 return std::nullopt;
-
286 }
-
287 else
-
288 return computedPrice;
-
289 }();
-
290 if (!payPrice)
-
291 return Unexpected(tecAMM_FAILED);
-
292 else if (payPrice > lpTokens)
-
293 return Unexpected(tecAMM_INVALID_TOKENS);
-
294 return *payPrice;
-
295 };
-
296
-
297 // No one owns the slot or expired slot.
-
298 if (auto const acct = auctionSlot[~sfAccount]; !acct || !validOwner(*acct))
-
299 {
-
300 if (auto const payPrice = getPayPrice(minSlotPrice); !payPrice)
-
301 return {payPrice.error(), false};
-
302 else
-
303 res = updateSlot(discountedFee, *payPrice, *payPrice);
-
304 }
-
305 else
-
306 {
-
307 // Price the slot was purchased at.
-
308 STAmount const pricePurchased = auctionSlot[sfPrice];
-
309 XRPL_ASSERT(timeSlot, "ripple::applyBid : timeSlot is set");
-
310 auto const fractionUsed =
-
311 (Number(*timeSlot) + 1) / AUCTION_SLOT_TIME_INTERVALS;
-
312 auto const fractionRemaining = Number(1) - fractionUsed;
-
313 auto const computedPrice = [&]() -> Number {
-
314 auto const p1_05 = Number(105, -2);
-
315 // First interval slot price
-
316 if (*timeSlot == 0)
-
317 return pricePurchased * p1_05 + minSlotPrice;
-
318 // Other intervals slot price
-
319 return pricePurchased * p1_05 * (1 - power(fractionUsed, 60)) +
-
320 minSlotPrice;
-
321 }();
-
322
-
323 auto const payPrice = getPayPrice(computedPrice);
-
324
-
325 if (!payPrice)
-
326 return {payPrice.error(), false};
-
327
-
328 // Refund the previous owner. If the time slot is 0 then
-
329 // the owner is refunded 95% of the amount.
-
330 auto const refund = fractionRemaining * pricePurchased;
-
331 if (refund > *payPrice)
-
332 {
-
333 // This error case should never occur.
-
334 JLOG(ctx_.journal.fatal()) << "AMM Bid: refund exceeds payPrice "
-
335 << refund << " " << *payPrice;
-
336 return {tecINTERNAL, false};
-
337 }
-
338 res = accountSend(
-
339 sb,
-
340 account_,
-
341 auctionSlot[sfAccount],
-
342 toSTAmount(lpTokens.issue(), refund),
-
343 ctx_.journal);
-
344 if (res != tesSUCCESS)
-
345 {
-
346 JLOG(ctx_.journal.debug()) << "AMM Bid: failed to refund.";
-
347 return {res, false};
-
348 }
-
349
-
350 auto const burn = *payPrice - refund;
-
351 res = updateSlot(discountedFee, *payPrice, burn);
-
352 }
-
353
-
354 return {res, res == tesSUCCESS};
-
355}
-
356
-
357TER
-
358AMMBid::doApply()
-
359{
-
360 // This is the ledger view that we work against. Transactions are applied
-
361 // as we go on processing transactions.
-
362 Sandbox sb(&ctx_.view());
-
363
-
364 auto const result = applyBid(ctx_, sb, account_, j_);
-
365 if (result.second)
-
366 sb.apply(ctx_.rawView());
-
367
-
368 return result.first;
-
369}
+
153 auto const bidMax = ctx.tx[~sfBidMax];
+
154 if (bidMax)
+
155 {
+
156 if (bidMax->issue() != lpTokens.issue())
+
157 {
+
158 JLOG(ctx.j.debug()) << "AMM Bid: Invalid LPToken.";
+
159 return temBAD_AMM_TOKENS;
+
160 }
+
161 if (*bidMax > lpTokens || *bidMax >= lpTokensBalance)
+
162 {
+
163 JLOG(ctx.j.debug()) << "AMM Bid: Invalid Tokens.";
+
164 return tecAMM_INVALID_TOKENS;
+
165 }
+
166 }
+
167
+
168 if (bidMin && bidMax && bidMin > bidMax)
+
169 {
+
170 JLOG(ctx.j.debug()) << "AMM Bid: Invalid Max/MinSlotPrice.";
+
171 return tecAMM_INVALID_TOKENS;
+
172 }
+
173
+
174 return tesSUCCESS;
+
175}
+
176
+
177static std::pair<TER, bool>
+
178applyBid(
+
179 ApplyContext& ctx_,
+
180 Sandbox& sb,
+
181 AccountID const& account_,
+
182 beast::Journal j_)
+
183{
+
184 using namespace std::chrono;
+
185 auto const ammSle =
+
186 sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
+
187 if (!ammSle)
+
188 return {tecINTERNAL, false};
+
189 STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
+
190 auto const lpTokens = ammLPHolds(sb, *ammSle, account_, ctx_.journal);
+
191 auto const& rules = ctx_.view().rules();
+
192 if (!rules.enabled(fixInnerObjTemplate))
+
193 {
+
194 if (!ammSle->isFieldPresent(sfAuctionSlot))
+
195 ammSle->makeFieldPresent(sfAuctionSlot);
+
196 }
+
197 else
+
198 {
+
199 XRPL_ASSERT(
+
200 ammSle->isFieldPresent(sfAuctionSlot),
+
201 "ripple::applyBid : has auction slot");
+
202 if (!ammSle->isFieldPresent(sfAuctionSlot))
+
203 return {tecINTERNAL, false};
+
204 }
+
205 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
+
206 auto const current =
+
207 duration_cast<seconds>(
+
208 ctx_.view().info().parentCloseTime.time_since_epoch())
+
209 .count();
+
210 // Auction slot discounted fee
+
211 auto const discountedFee =
+
212 (*ammSle)[sfTradingFee] / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION;
+
213 auto const tradingFee = getFee((*ammSle)[sfTradingFee]);
+
214 // Min price
+
215 auto const minSlotPrice =
+
216 lptAMMBalance * tradingFee / AUCTION_SLOT_MIN_FEE_FRACTION;
+
217
+
218 std::uint32_t constexpr tailingSlot = AUCTION_SLOT_TIME_INTERVALS - 1;
+
219
+
220 // If seated then it is the current slot-holder time slot, otherwise
+
221 // the auction slot is not owned. Slot range is in {0-19}
+
222 auto const timeSlot = ammAuctionTimeSlot(current, auctionSlot);
+
223
+
224 // Account must exist and the slot not expired.
+
225 auto validOwner = [&](AccountID const& account) {
+
226 // Valid range is 0-19 but the tailing slot pays MinSlotPrice
+
227 // and doesn't refund so the check is < instead of <= to optimize.
+
228 return timeSlot && *timeSlot < tailingSlot &&
+
229 sb.read(keylet::account(account));
+
230 };
+
231
+
232 auto updateSlot = [&](std::uint32_t fee,
+
233 Number const& minPrice,
+
234 Number const& burn) -> TER {
+
235 auctionSlot.setAccountID(sfAccount, account_);
+
236 auctionSlot.setFieldU32(sfExpiration, current + TOTAL_TIME_SLOT_SECS);
+
237 if (fee != 0)
+
238 auctionSlot.setFieldU16(sfDiscountedFee, fee);
+
239 else if (auctionSlot.isFieldPresent(sfDiscountedFee))
+
240 auctionSlot.makeFieldAbsent(sfDiscountedFee);
+
241 auctionSlot.setFieldAmount(
+
242 sfPrice, toSTAmount(lpTokens.issue(), minPrice));
+
243 if (ctx_.tx.isFieldPresent(sfAuthAccounts))
+
244 auctionSlot.setFieldArray(
+
245 sfAuthAccounts, ctx_.tx.getFieldArray(sfAuthAccounts));
+
246 else
+
247 auctionSlot.makeFieldAbsent(sfAuthAccounts);
+
248 // Burn the remaining bid amount
+
249 auto const saBurn = adjustLPTokens(
+
250 lptAMMBalance,
+
251 toSTAmount(lptAMMBalance.issue(), burn),
+
252 IsDeposit::No);
+
253 if (saBurn >= lptAMMBalance)
+
254 {
+
255 // This error case should never occur.
+
256 JLOG(ctx_.journal.fatal())
+
257 << "AMM Bid: LP Token burn exceeds AMM balance " << burn << " "
+
258 << lptAMMBalance;
+
259 return tecINTERNAL;
+
260 }
+
261 auto res =
+
262 redeemIOU(sb, account_, saBurn, lpTokens.issue(), ctx_.journal);
+
263 if (res != tesSUCCESS)
+
264 {
+
265 JLOG(ctx_.journal.debug()) << "AMM Bid: failed to redeem.";
+
266 return res;
+
267 }
+
268 ammSle->setFieldAmount(sfLPTokenBalance, lptAMMBalance - saBurn);
+
269 sb.update(ammSle);
+
270 return tesSUCCESS;
+
271 };
+
272
+
273 TER res = tesSUCCESS;
+
274
+
275 auto const bidMin = ctx_.tx[~sfBidMin];
+
276 auto const bidMax = ctx_.tx[~sfBidMax];
+
277
+
278 auto getPayPrice =
+
279 [&](Number const& computedPrice) -> Expected<Number, TER> {
+
280 auto const payPrice = [&]() -> std::optional<Number> {
+
281 // Both min/max bid price are defined
+
282 if (bidMin && bidMax)
+
283 {
+
284 if (computedPrice <= *bidMax)
+
285 return std::max(computedPrice, Number(*bidMin));
+
286 JLOG(ctx_.journal.debug())
+
287 << "AMM Bid: not in range " << computedPrice << " "
+
288 << *bidMin << " " << *bidMax;
+
289 return std::nullopt;
+
290 }
+
291 // Bidder pays max(bidPrice, computedPrice)
+
292 if (bidMin)
+
293 {
+
294 return std::max(computedPrice, Number(*bidMin));
+
295 }
+
296 else if (bidMax)
+
297 {
+
298 if (computedPrice <= *bidMax)
+
299 return computedPrice;
+
300 JLOG(ctx_.journal.debug()) << "AMM Bid: not in range "
+
301 << computedPrice << " " << *bidMax;
+
302 return std::nullopt;
+
303 }
+
304 else
+
305 return computedPrice;
+
306 }();
+
307 if (!payPrice)
+
308 return Unexpected(tecAMM_FAILED);
+
309 else if (payPrice > lpTokens)
+
310 return Unexpected(tecAMM_INVALID_TOKENS);
+
311 return *payPrice;
+
312 };
+
313
+
314 // No one owns the slot or expired slot.
+
315 if (auto const acct = auctionSlot[~sfAccount]; !acct || !validOwner(*acct))
+
316 {
+
317 if (auto const payPrice = getPayPrice(minSlotPrice); !payPrice)
+
318 return {payPrice.error(), false};
+
319 else
+
320 res = updateSlot(discountedFee, *payPrice, *payPrice);
+
321 }
+
322 else
+
323 {
+
324 // Price the slot was purchased at.
+
325 STAmount const pricePurchased = auctionSlot[sfPrice];
+
326 XRPL_ASSERT(timeSlot, "ripple::applyBid : timeSlot is set");
+
327 auto const fractionUsed =
+
328 (Number(*timeSlot) + 1) / AUCTION_SLOT_TIME_INTERVALS;
+
329 auto const fractionRemaining = Number(1) - fractionUsed;
+
330 auto const computedPrice = [&]() -> Number {
+
331 auto const p1_05 = Number(105, -2);
+
332 // First interval slot price
+
333 if (*timeSlot == 0)
+
334 return pricePurchased * p1_05 + minSlotPrice;
+
335 // Other intervals slot price
+
336 return pricePurchased * p1_05 * (1 - power(fractionUsed, 60)) +
+
337 minSlotPrice;
+
338 }();
+
339
+
340 auto const payPrice = getPayPrice(computedPrice);
+
341
+
342 if (!payPrice)
+
343 return {payPrice.error(), false};
+
344
+
345 // Refund the previous owner. If the time slot is 0 then
+
346 // the owner is refunded 95% of the amount.
+
347 auto const refund = fractionRemaining * pricePurchased;
+
348 if (refund > *payPrice)
+
349 {
+
350 // This error case should never occur.
+
351 JLOG(ctx_.journal.fatal()) << "AMM Bid: refund exceeds payPrice "
+
352 << refund << " " << *payPrice;
+
353 return {tecINTERNAL, false};
+
354 }
+
355 res = accountSend(
+
356 sb,
+
357 account_,
+
358 auctionSlot[sfAccount],
+
359 toSTAmount(lpTokens.issue(), refund),
+
360 ctx_.journal);
+
361 if (res != tesSUCCESS)
+
362 {
+
363 JLOG(ctx_.journal.debug()) << "AMM Bid: failed to refund.";
+
364 return {res, false};
+
365 }
+
366
+
367 auto const burn = *payPrice - refund;
+
368 res = updateSlot(discountedFee, *payPrice, burn);
+
369 }
370
-
371} // namespace ripple
+
371 return {res, res == tesSUCCESS};
+
372}
+
373
+
374TER
+
375AMMBid::doApply()
+
376{
+
377 // This is the ledger view that we work against. Transactions are applied
+
378 // as we go on processing transactions.
+
379 Sandbox sb(&ctx_.view());
+
380
+
381 auto const result = applyBid(ctx_, sb, account_, j_);
+
382 if (result.second)
+
383 sb.apply(ctx_.rawView());
+
384
+
385 return result.first;
+
386}
+
387
+
388} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:60
Stream fatal() const
Definition: Journal.h:352
Stream debug() const
Definition: Journal.h:328
-
TER doApply() override
Definition: AMMBid.cpp:358
-
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMBid.cpp:87
+
TER doApply() override
Definition: AMMBid.cpp:375
+
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMBid.cpp:102
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMBid.cpp:34
State information when applying a tx.
Definition: ApplyContext.h:37
RawView & rawView()
Definition: ApplyContext.h:91
@@ -464,6 +481,7 @@ $(function() {
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual LedgerInfo const & info() const =0
Returns information about the ledger.
virtual Rules const & rules() const =0
Returns the tx processing rules.
+
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
Definition: STAmount.h:50
Issue const & issue() const
Definition: STAmount.h:496
size_type size() const
Definition: STArray.h:248
@@ -487,7 +505,6 @@ $(function() {
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:184
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition: AMMCore.cpp:95
-
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, bool isDeposit)
Adjust LP tokens to deposit/withdraw.
Definition: AMMHelpers.cpp:133
std::uint32_t constexpr TOTAL_TIME_SLOT_SECS
Definition: AMMCore.h:34
std::uint16_t constexpr AUCTION_SLOT_TIME_INTERVALS
Definition: AMMCore.h:35
std::optional< std::uint8_t > ammAuctionTimeSlot(std::uint64_t current, STObject const &auctionSlot)
Get time slot of the auction slot.
Definition: AMMCore.cpp:108
@@ -497,6 +514,7 @@ $(function() {
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:91
@ current
This was a new validation and was added.
std::uint32_t constexpr AUCTION_SLOT_MIN_FEE_FRACTION
Definition: AMMCore.h:39
+
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
Definition: AMMHelpers.cpp:173
STAmount ammLPHolds(ReadView const &view, Currency const &cur1, Currency const &cur2, AccountID const &ammAccount, AccountID const &lpAccount, beast::Journal const j)
Get the balance of LP tokens.
Definition: AMMUtils.cpp:112
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:160
@ tecAMM_EMPTY
Definition: TER.h:332
@@ -505,11 +523,12 @@ $(function() {
@ tecAMM_INVALID_TOKENS
Definition: TER.h:331
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition: AMMCore.h:101
Number power(Number const &f, unsigned n)
Definition: Number.cpp:613
+
@ No
@ tesSUCCESS
Definition: TER.h:244
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition: AMMCore.cpp:80
bool isTesSuccess(TER x) noexcept
Definition: TER.h:672
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:63
-
static std::pair< TER, bool > applyBid(ApplyContext &ctx_, Sandbox &sb, AccountID const &account_, beast::Journal j_)
Definition: AMMBid.cpp:163
+
static std::pair< TER, bool > applyBid(ApplyContext &ctx_, Sandbox &sb, AccountID const &account_, beast::Journal j_)
Definition: AMMBid.cpp:178
@ terNO_ACCOUNT
Definition: TER.h:217
@ terNO_AMM
Definition: TER.h:227
TERSubset< CanCvtToTER > TER
Definition: TER.h:643
@@ -524,6 +543,7 @@ $(function() {
+
NetClock::time_point parentCloseTime
Definition: LedgerHeader.h:42
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:79
ReadView const & view
Definition: Transactor.h:82
diff --git a/AMMBid_8h_source.html b/AMMBid_8h_source.html index c0a44f1ab1..4335d78b93 100644 --- a/AMMBid_8h_source.html +++ b/AMMBid_8h_source.html @@ -126,8 +126,8 @@ $(function() {
86#endif // RIPPLE_TX_AMMBID_H_INCLUDED
AMMBid implements AMM bid Transactor.
Definition: AMMBid.h:66
static constexpr ConsequencesFactoryType ConsequencesFactory
Definition: AMMBid.h:68
-
TER doApply() override
Definition: AMMBid.cpp:358
-
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMBid.cpp:87
+
TER doApply() override
Definition: AMMBid.cpp:375
+
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMBid.cpp:102
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMBid.cpp:34
AMMBid(ApplyContext &ctx)
Definition: AMMBid.h:70
State information when applying a tx.
Definition: ApplyContext.h:37
diff --git a/AMMCalc__test_8cpp_source.html b/AMMCalc__test_8cpp_source.html index 36bba4b199..3ee985f5d3 100644 --- a/AMMCalc__test_8cpp_source.html +++ b/AMMCalc__test_8cpp_source.html @@ -573,16 +573,16 @@ $(function() {
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
static void limitStepIn(Offer const &offer, TAmounts< TIn, TOut > &ofrAmt, TAmounts< TIn, TOut > &stpAmt, TOut &ownerGives, std::uint32_t transferRateIn, std::uint32_t transferRateOut, TIn const &limit)
Definition: BookStep.cpp:660
-
TOut swapAssetIn(TAmounts< TIn, TOut > const &pool, TIn const &assetIn, std::uint16_t tfee)
AMM pool invariant - the product (A * B) after swap in/out has to remain at least the same: (A + in) ...
Definition: AMMHelpers.h:462
+
TOut swapAssetIn(TAmounts< TIn, TOut > const &pool, TIn const &assetIn, std::uint16_t tfee)
AMM pool invariant - the product (A * B) after swap in/out has to remain at least the same: (A + in) ...
Definition: AMMHelpers.h:464
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
-
std::optional< TAmounts< TIn, TOut > > changeSpotPriceQuality(TAmounts< TIn, TOut > const &pool, Quality const &quality, std::uint16_t tfee, Rules const &rules, beast::Journal j)
Generate AMM offer so that either updated Spot Price Quality (SPQ) is equal to LOB quality (in this c...
Definition: AMMHelpers.h:329
+
std::optional< TAmounts< TIn, TOut > > changeSpotPriceQuality(TAmounts< TIn, TOut > const &pool, Quality const &quality, std::uint16_t tfee, Rules const &rules, beast::Journal j)
Generate AMM offer so that either updated Spot Price Quality (SPQ) is equal to LOB quality (in this c...
Definition: AMMHelpers.h:331
static void limitStepOut(Offer const &offer, TAmounts< TIn, TOut > &ofrAmt, TAmounts< TIn, TOut > &stpAmt, TOut &ownerGives, std::uint32_t transferRateIn, std::uint32_t transferRateOut, TOut const &limit)
Definition: BookStep.cpp:691
@ match
IOUAmount mulRatio(IOUAmount const &amt, std::uint32_t num, std::uint32_t den, bool roundUp)
Definition: IOUAmount.cpp:190
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition: AMMHelpers.cpp:25
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
STAmount amountFromString(Asset const &asset, std::string const &amount)
Definition: STAmount.cpp:869
-
TIn swapAssetOut(TAmounts< TIn, TOut > const &pool, TOut const &assetOut, std::uint16_t tfee)
Swap assetOut out of the pool and swap in a proportional amount of the other asset.
Definition: AMMHelpers.h:535
+
TIn swapAssetOut(TAmounts< TIn, TOut > const &pool, TOut const &assetOut, std::uint16_t tfee)
Swap assetOut out of the pool and swap in a proportional amount of the other asset.
Definition: AMMHelpers.h:537
T push_back(T... args)
T stoll(T... args)
diff --git a/AMMClawback_8cpp_source.html b/AMMClawback_8cpp_source.html index 1db961d446..a2943d48c2 100644 --- a/AMMClawback_8cpp_source.html +++ b/AMMClawback_8cpp_source.html @@ -377,8 +377,8 @@ $(function() {
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMClawback.cpp:37
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMClawback.cpp:95
std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > equalWithdrawMatchingOneAmount(Sandbox &view, SLE const &ammSle, AccountID const &holder, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &holdLPtokens, STAmount const &amount)
Withdraw both assets by providing maximum amount of asset1, asset2's amount will be calculated accord...
-
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > equalWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const account, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHanding, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Equal-asset withdrawal (LPTokens) of some AMM instance pools shares represented by the number of LPTo...
-
static std::pair< TER, bool > deleteAMMAccountIfEmpty(Sandbox &sb, std::shared_ptr< SLE > const ammSle, STAmount const &lpTokenBalance, Issue const &issue1, Issue const &issue2, beast::Journal const &journal)
+
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > equalWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const account, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHanding, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Equal-asset withdrawal (LPTokens) of some AMM instance pools shares represented by the number of LPTo...
+
static std::pair< TER, bool > deleteAMMAccountIfEmpty(Sandbox &sb, std::shared_ptr< SLE > const ammSle, STAmount const &lpTokenBalance, Issue const &issue1, Issue const &issue2, beast::Journal const &journal)
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > withdraw(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, AccountID const &account, STAmount const &amountBalance, STAmount const &amountWithdraw, std::optional< STAmount > const &amount2Withdraw, STAmount const &lpTokensAMMBalance, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHandling, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Withdraw requested assets and token from AMM into LP account.
RawView & rawView()
Definition: ApplyContext.h:91
ApplyView & view()
Definition: ApplyContext.h:78
diff --git a/AMMClawback__test_8cpp_source.html b/AMMClawback__test_8cpp_source.html index 7277fc6b45..37c4306adf 100644 --- a/AMMClawback__test_8cpp_source.html +++ b/AMMClawback__test_8cpp_source.html @@ -659,1217 +659,1482 @@ $(function() {
581 AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
582 env.close();
583
-
584 BEAST_EXPECT(amm.expectBalances(
-
585 USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
-
586
-
587 // gw clawback 1000 USD from the AMM pool
-
588 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
-
589 ter(tesSUCCESS));
-
590 env.close();
-
591
-
592 // Alice's initial balance for USD is 6000 USD. Alice deposited 4000
-
593 // USD into the pool, then she has 2000 USD. And 1000 USD was clawed
-
594 // back from the AMM pool, so she still has 2000 USD.
-
595 env.require(balance(alice, gw["USD"](2000)));
-
596
-
597 // Alice's initial balance for EUR is 6000 EUR. Alice deposited 5000
-
598 // EUR into the pool, 1250 EUR was withdrawn proportionally. So she
-
599 // has 2500 EUR now.
-
600 env.require(balance(alice, gw2["EUR"](2250)));
-
601
-
602 // 1000 USD and 1250 EUR was withdrawn from the AMM pool, so the
-
603 // current balance is 3000 USD and 3750 EUR.
-
604 BEAST_EXPECT(amm.expectBalances(
-
605 USD(3000), EUR(3750), IOUAmount{3354101966249685, -12}));
-
606
-
607 // Alice has 3/4 of its initial lptokens Left.
-
608 BEAST_EXPECT(
-
609 amm.expectLPTokens(alice, IOUAmount{3354101966249685, -12}));
-
610
-
611 // gw clawback another 500 USD from the AMM pool.
-
612 env(amm::ammClawback(gw, alice, USD, EUR, USD(500)),
-
613 ter(tesSUCCESS));
-
614 env.close();
-
615
-
616 // Alice should still has 2000 USD because gw clawed back from the
-
617 // AMM pool.
-
618 env.require(balance(alice, gw["USD"](2000)));
-
619
-
620 BEAST_EXPECT(amm.expectBalances(
-
621 STAmount{USD, UINT64_C(2500000000000001), -12},
-
622 STAmount{EUR, UINT64_C(3125000000000001), -12},
-
623 IOUAmount{2795084971874738, -12}));
-
624
-
625 BEAST_EXPECT(
-
626 env.balance(alice, EUR) ==
-
627 STAmount(EUR, UINT64_C(2874999999999999), -12));
-
628
-
629 // gw clawback small amount, 1 USD.
-
630 env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), ter(tesSUCCESS));
-
631 env.close();
-
632
-
633 // Another 1 USD / 1.25 EUR was withdrawn.
-
634 env.require(balance(alice, gw["USD"](2000)));
-
635
-
636 BEAST_EXPECT(amm.expectBalances(
-
637 STAmount{USD, UINT64_C(2499000000000002), -12},
-
638 STAmount{EUR, UINT64_C(3123750000000002), -12},
-
639 IOUAmount{2793966937885989, -12}));
+
584 if (!features[fixAMMv1_3])
+
585 BEAST_EXPECT(amm.expectBalances(
+
586 USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
+
587 else
+
588 BEAST_EXPECT(amm.expectBalances(
+
589 USD(4000), EUR(5000), IOUAmount{4472135954999579, -12}));
+
590
+
591 // gw clawback 1000 USD from the AMM pool
+
592 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
+
593 ter(tesSUCCESS));
+
594 env.close();
+
595
+
596 // Alice's initial balance for USD is 6000 USD. Alice deposited 4000
+
597 // USD into the pool, then she has 2000 USD. And 1000 USD was clawed
+
598 // back from the AMM pool, so she still has 2000 USD.
+
599 env.require(balance(alice, gw["USD"](2000)));
+
600
+
601 // Alice's initial balance for EUR is 6000 EUR. Alice deposited 5000
+
602 // EUR into the pool, 1250 EUR was withdrawn proportionally. So she
+
603 // has 2500 EUR now.
+
604 env.require(balance(alice, gw2["EUR"](2250)));
+
605
+
606 // 1000 USD and 1250 EUR was withdrawn from the AMM pool, so the
+
607 // current balance is 3000 USD and 3750 EUR.
+
608 if (!features[fixAMMv1_3])
+
609 BEAST_EXPECT(amm.expectBalances(
+
610 USD(3000), EUR(3750), IOUAmount{3354101966249685, -12}));
+
611 else
+
612 BEAST_EXPECT(amm.expectBalances(
+
613 USD(3000), EUR(3750), IOUAmount{3354101966249684, -12}));
+
614
+
615 // Alice has 3/4 of its initial lptokens Left.
+
616 if (!features[fixAMMv1_3])
+
617 BEAST_EXPECT(amm.expectLPTokens(
+
618 alice, IOUAmount{3354101966249685, -12}));
+
619 else
+
620 BEAST_EXPECT(amm.expectLPTokens(
+
621 alice, IOUAmount{3354101966249684, -12}));
+
622
+
623 // gw clawback another 500 USD from the AMM pool.
+
624 env(amm::ammClawback(gw, alice, USD, EUR, USD(500)),
+
625 ter(tesSUCCESS));
+
626 env.close();
+
627
+
628 // Alice should still has 2000 USD because gw clawed back from the
+
629 // AMM pool.
+
630 env.require(balance(alice, gw["USD"](2000)));
+
631
+
632 if (!features[fixAMMv1_3])
+
633 BEAST_EXPECT(amm.expectBalances(
+
634 STAmount{USD, UINT64_C(2500000000000001), -12},
+
635 STAmount{EUR, UINT64_C(3125000000000001), -12},
+
636 IOUAmount{2795084971874738, -12}));
+
637 else
+
638 BEAST_EXPECT(amm.expectBalances(
+
639 USD(2500), EUR(3125), IOUAmount{2795084971874737, -12}));
640
-
641 BEAST_EXPECT(
-
642 env.balance(alice, EUR) ==
-
643 STAmount(EUR, UINT64_C(2876249999999998), -12));
-
644
-
645 // gw clawback 4000 USD, exceeding the current balance. We
-
646 // will clawback all.
-
647 env(amm::ammClawback(gw, alice, USD, EUR, USD(4000)),
-
648 ter(tesSUCCESS));
-
649 env.close();
-
650
-
651 env.require(balance(alice, gw["USD"](2000)));
-
652
-
653 // All alice's EUR in the pool goes back to alice.
-
654 BEAST_EXPECT(
-
655 env.balance(alice, EUR) ==
-
656 STAmount(EUR, UINT64_C(6000000000000000), -12));
-
657
-
658 // amm is automatically deleted.
-
659 BEAST_EXPECT(!amm.ammExists());
-
660 }
-
661
-
662 // Test AMMClawback for USD/XRP pool. Claw back USD for multiple times,
-
663 // and XRP goes back to the holder. The last AMMClawback transaction
-
664 // exceeds the holder's USD balance in AMM pool. In this case, gw
-
665 // creates the AMM pool USD/XRP, both alice and bob deposit into it. gw2
-
666 // creates the AMM pool EUR/XRP.
-
667 {
-
668 Env env(*this, features);
-
669 Account gw{"gateway"};
-
670 Account gw2{"gateway2"};
-
671 Account alice{"alice"};
-
672 Account bob{"bob"};
-
673 env.fund(XRP(1000000), gw, gw2, alice, bob);
-
674 env.close();
-
675
-
676 // gw sets asfAllowTrustLineClawback.
-
677 env(fset(gw, asfAllowTrustLineClawback));
-
678 env.close();
-
679 env.require(flags(gw, asfAllowTrustLineClawback));
-
680
-
681 // gw2 sets asfAllowTrustLineClawback.
-
682 env(fset(gw2, asfAllowTrustLineClawback));
-
683 env.close();
-
684 env.require(flags(gw2, asfAllowTrustLineClawback));
-
685
-
686 // gw issues 6000 USD to Alice and 5000 USD to Bob.
-
687 auto const USD = gw["USD"];
-
688 env.trust(USD(100000), alice);
-
689 env(pay(gw, alice, USD(6000)));
-
690 env.trust(USD(100000), bob);
-
691 env(pay(gw, bob, USD(5000)));
-
692 env.close();
-
693
-
694 // gw2 issues 5000 EUR to Alice and 4000 EUR to Bob.
-
695 auto const EUR = gw2["EUR"];
-
696 env.trust(EUR(100000), alice);
-
697 env(pay(gw2, alice, EUR(5000)));
-
698 env.trust(EUR(100000), bob);
-
699 env(pay(gw2, bob, EUR(4000)));
+
641 if (!features[fixAMMv1_3])
+
642 BEAST_EXPECT(
+
643 env.balance(alice, EUR) ==
+
644 STAmount(EUR, UINT64_C(2874999999999999), -12));
+
645 else
+
646 BEAST_EXPECT(env.balance(alice, EUR) == EUR(2875));
+
647
+
648 // gw clawback small amount, 1 USD.
+
649 env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), ter(tesSUCCESS));
+
650 env.close();
+
651
+
652 // Another 1 USD / 1.25 EUR was withdrawn.
+
653 env.require(balance(alice, gw["USD"](2000)));
+
654
+
655 if (!features[fixAMMv1_3])
+
656 BEAST_EXPECT(amm.expectBalances(
+
657 STAmount{USD, UINT64_C(2499000000000002), -12},
+
658 STAmount{EUR, UINT64_C(3123750000000002), -12},
+
659 IOUAmount{2793966937885989, -12}));
+
660 else
+
661 BEAST_EXPECT(amm.expectBalances(
+
662 USD(2499), EUR(3123.75), IOUAmount{2793966937885987, -12}));
+
663
+
664 if (!features[fixAMMv1_3])
+
665 BEAST_EXPECT(
+
666 env.balance(alice, EUR) ==
+
667 STAmount(EUR, UINT64_C(2'876'249999999998), -12));
+
668 else
+
669 BEAST_EXPECT(env.balance(alice, EUR) == EUR(2876.25));
+
670
+
671 // gw clawback 4000 USD, exceeding the current balance. We
+
672 // will clawback all.
+
673 env(amm::ammClawback(gw, alice, USD, EUR, USD(4000)),
+
674 ter(tesSUCCESS));
+
675 env.close();
+
676
+
677 env.require(balance(alice, gw["USD"](2000)));
+
678
+
679 // All alice's EUR in the pool goes back to alice.
+
680 BEAST_EXPECT(
+
681 env.balance(alice, EUR) ==
+
682 STAmount(EUR, UINT64_C(6000000000000000), -12));
+
683
+
684 // amm is automatically deleted.
+
685 BEAST_EXPECT(!amm.ammExists());
+
686 }
+
687
+
688 // Test AMMClawback for USD/XRP pool. Claw back USD for multiple times,
+
689 // and XRP goes back to the holder. The last AMMClawback transaction
+
690 // exceeds the holder's USD balance in AMM pool. In this case, gw
+
691 // creates the AMM pool USD/XRP, both alice and bob deposit into it. gw2
+
692 // creates the AMM pool EUR/XRP.
+
693 {
+
694 Env env(*this, features);
+
695 Account gw{"gateway"};
+
696 Account gw2{"gateway2"};
+
697 Account alice{"alice"};
+
698 Account bob{"bob"};
+
699 env.fund(XRP(1000000), gw, gw2, alice, bob);
700 env.close();
701
-
702 // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD.
-
703 AMM amm(env, gw, XRP(2000), USD(1000), ter(tesSUCCESS));
-
704 BEAST_EXPECT(amm.expectBalances(
-
705 USD(1000), XRP(2000), IOUAmount{1414213562373095, -9}));
-
706 amm.deposit(alice, USD(1000), XRP(2000));
-
707 BEAST_EXPECT(amm.expectBalances(
-
708 USD(2000), XRP(4000), IOUAmount{2828427124746190, -9}));
-
709 amm.deposit(bob, USD(1000), XRP(2000));
-
710 BEAST_EXPECT(amm.expectBalances(
-
711 USD(3000), XRP(6000), IOUAmount{4242640687119285, -9}));
-
712 env.close();
-
713
-
714 // gw2 creates AMM pool of XRP/EUR, alice and bob deposit XRP/EUR.
-
715 AMM amm2(env, gw2, XRP(3000), EUR(1000), ter(tesSUCCESS));
-
716 BEAST_EXPECT(amm2.expectBalances(
-
717 EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
-
718 amm2.deposit(alice, EUR(1000), XRP(3000));
-
719 BEAST_EXPECT(amm2.expectBalances(
-
720 EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
-
721 amm2.deposit(bob, EUR(1000), XRP(3000));
-
722 BEAST_EXPECT(amm2.expectBalances(
-
723 EUR(3000), XRP(9000), IOUAmount{5196152422706634, -9}));
-
724 env.close();
-
725
-
726 auto aliceXrpBalance = env.balance(alice, XRP);
-
727 auto bobXrpBalance = env.balance(bob, XRP);
-
728
-
729 // gw clawback 500 USD from alice in amm
-
730 env(amm::ammClawback(gw, alice, USD, XRP, USD(500)),
-
731 ter(tesSUCCESS));
-
732 env.close();
-
733
-
734 // Alice's initial balance for USD is 6000 USD. Alice deposited 1000
-
735 // USD into the pool, then she has 5000 USD. And 500 USD was clawed
-
736 // back from the AMM pool, so she still has 5000 USD.
-
737 env.require(balance(alice, gw["USD"](5000)));
-
738
-
739 // Bob's balance is not changed.
-
740 env.require(balance(bob, gw["USD"](4000)));
-
741
-
742 // Alice gets 1000 XRP back.
-
743 BEAST_EXPECT(
-
744 expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000)));
-
745
-
746 BEAST_EXPECT(amm.expectBalances(
-
747 USD(2500), XRP(5000), IOUAmount{3535533905932738, -9}));
-
748 BEAST_EXPECT(
-
749 amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10}));
-
750 BEAST_EXPECT(
-
751 amm.expectLPTokens(bob, IOUAmount{1414213562373095, -9}));
-
752
-
753 // gw clawback 10 USD from bob in amm.
-
754 env(amm::ammClawback(gw, bob, USD, XRP, USD(10)), ter(tesSUCCESS));
-
755 env.close();
-
756
-
757 env.require(balance(alice, gw["USD"](5000)));
-
758 env.require(balance(bob, gw["USD"](4000)));
-
759
-
760 // Bob gets 20 XRP back.
-
761 BEAST_EXPECT(
-
762 expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(20)));
-
763 BEAST_EXPECT(amm.expectBalances(
-
764 STAmount{USD, UINT64_C(2490000000000001), -12},
-
765 XRP(4980),
-
766 IOUAmount{3521391770309008, -9}));
-
767 BEAST_EXPECT(
-
768 amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10}));
-
769 BEAST_EXPECT(
-
770 amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
+
702 // gw sets asfAllowTrustLineClawback.
+
703 env(fset(gw, asfAllowTrustLineClawback));
+
704 env.close();
+
705 env.require(flags(gw, asfAllowTrustLineClawback));
+
706
+
707 // gw2 sets asfAllowTrustLineClawback.
+
708 env(fset(gw2, asfAllowTrustLineClawback));
+
709 env.close();
+
710 env.require(flags(gw2, asfAllowTrustLineClawback));
+
711
+
712 // gw issues 6000 USD to Alice and 5000 USD to Bob.
+
713 auto const USD = gw["USD"];
+
714 env.trust(USD(100000), alice);
+
715 env(pay(gw, alice, USD(6000)));
+
716 env.trust(USD(100000), bob);
+
717 env(pay(gw, bob, USD(5000)));
+
718 env.close();
+
719
+
720 // gw2 issues 5000 EUR to Alice and 4000 EUR to Bob.
+
721 auto const EUR = gw2["EUR"];
+
722 env.trust(EUR(100000), alice);
+
723 env(pay(gw2, alice, EUR(5000)));
+
724 env.trust(EUR(100000), bob);
+
725 env(pay(gw2, bob, EUR(4000)));
+
726 env.close();
+
727
+
728 // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD.
+
729 AMM amm(env, gw, XRP(2000), USD(1000), ter(tesSUCCESS));
+
730 BEAST_EXPECT(amm.expectBalances(
+
731 USD(1000), XRP(2000), IOUAmount{1414213562373095, -9}));
+
732 amm.deposit(alice, USD(1000), XRP(2000));
+
733 BEAST_EXPECT(amm.expectBalances(
+
734 USD(2000), XRP(4000), IOUAmount{2828427124746190, -9}));
+
735 amm.deposit(bob, USD(1000), XRP(2000));
+
736 BEAST_EXPECT(amm.expectBalances(
+
737 USD(3000), XRP(6000), IOUAmount{4242640687119285, -9}));
+
738 env.close();
+
739
+
740 // gw2 creates AMM pool of XRP/EUR, alice and bob deposit XRP/EUR.
+
741 AMM amm2(env, gw2, XRP(3000), EUR(1000), ter(tesSUCCESS));
+
742 if (!features[fixAMMv1_3])
+
743 BEAST_EXPECT(amm2.expectBalances(
+
744 EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
+
745 else
+
746 BEAST_EXPECT(amm2.expectBalances(
+
747 EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9}));
+
748 amm2.deposit(alice, EUR(1000), XRP(3000));
+
749 if (!features[fixAMMv1_3])
+
750 BEAST_EXPECT(amm2.expectBalances(
+
751 EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
+
752 else
+
753 BEAST_EXPECT(amm2.expectBalances(
+
754 EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9}));
+
755 amm2.deposit(bob, EUR(1000), XRP(3000));
+
756 if (!features[fixAMMv1_3])
+
757 BEAST_EXPECT(amm2.expectBalances(
+
758 EUR(3000), XRP(9000), IOUAmount{5196152422706634, -9}));
+
759 else
+
760 BEAST_EXPECT(amm2.expectBalances(
+
761 EUR(3000), XRP(9000), IOUAmount{5196152422706631, -9}));
+
762 env.close();
+
763
+
764 auto aliceXrpBalance = env.balance(alice, XRP);
+
765 auto bobXrpBalance = env.balance(bob, XRP);
+
766
+
767 // gw clawback 500 USD from alice in amm
+
768 env(amm::ammClawback(gw, alice, USD, XRP, USD(500)),
+
769 ter(tesSUCCESS));
+
770 env.close();
771
-
772 // gw2 clawback 200 EUR from amm2.
-
773 env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(200)),
-
774 ter(tesSUCCESS));
-
775 env.close();
+
772 // Alice's initial balance for USD is 6000 USD. Alice deposited 1000
+
773 // USD into the pool, then she has 5000 USD. And 500 USD was clawed
+
774 // back from the AMM pool, so she still has 5000 USD.
+
775 env.require(balance(alice, gw["USD"](5000)));
776
-
777 env.require(balance(alice, gw2["EUR"](4000)));
-
778 env.require(balance(bob, gw2["EUR"](3000)));
+
777 // Bob's balance is not changed.
+
778 env.require(balance(bob, gw["USD"](4000)));
779
-
780 // Alice gets 600 XRP back.
-
781 BEAST_EXPECT(expectLedgerEntryRoot(
-
782 env, alice, aliceXrpBalance + XRP(1000) + XRP(600)));
-
783 BEAST_EXPECT(amm2.expectBalances(
-
784 EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9}));
-
785 BEAST_EXPECT(
-
786 amm2.expectLPTokens(alice, IOUAmount{1385640646055103, -9}));
-
787 BEAST_EXPECT(
-
788 amm2.expectLPTokens(bob, IOUAmount{1732050807568878, -9}));
-
789
-
790 // gw claw back 1000 USD from alice in amm, which exceeds alice's
-
791 // balance. This will clawback all the remaining LP tokens of alice
-
792 // (corresponding 500 USD / 1000 XRP).
-
793 env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
-
794 ter(tesSUCCESS));
-
795 env.close();
-
796
-
797 env.require(balance(alice, gw["USD"](5000)));
-
798 env.require(balance(bob, gw["USD"](4000)));
-
799
-
800 // Alice gets 1000 XRP back.
-
801 BEAST_EXPECT(expectLedgerEntryRoot(
-
802 env,
-
803 alice,
-
804 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
-
805 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
-
806 BEAST_EXPECT(
-
807 amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
-
808 BEAST_EXPECT(amm.expectBalances(
-
809 STAmount{USD, UINT64_C(1990000000000001), -12},
-
810 XRP(3980),
-
811 IOUAmount{2814284989122460, -9}));
-
812
-
813 // gw clawback 1000 USD from bob in amm, which also exceeds bob's
-
814 // balance in amm. All bob's lptoken in amm will be consumed, which
-
815 // corresponds to 990 USD / 1980 XRP
-
816 env(amm::ammClawback(gw, bob, USD, XRP, USD(1000)),
-
817 ter(tesSUCCESS));
-
818 env.close();
-
819
-
820 env.require(balance(alice, gw["USD"](5000)));
-
821 env.require(balance(bob, gw["USD"](4000)));
-
822
-
823 BEAST_EXPECT(expectLedgerEntryRoot(
-
824 env,
-
825 alice,
-
826 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
-
827 BEAST_EXPECT(expectLedgerEntryRoot(
-
828 env, bob, bobXrpBalance + XRP(20) + XRP(1980)));
+
780 // Alice gets 1000 XRP back.
+
781 BEAST_EXPECT(
+
782 expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000)));
+
783
+
784 if (!features[fixAMMv1_3])
+
785 BEAST_EXPECT(amm.expectBalances(
+
786 USD(2500), XRP(5000), IOUAmount{3535533905932738, -9}));
+
787 else
+
788 BEAST_EXPECT(amm.expectBalances(
+
789 USD(2500), XRP(5000), IOUAmount{3535533905932737, -9}));
+
790 if (!features[fixAMMv1_3])
+
791 BEAST_EXPECT(amm.expectLPTokens(
+
792 alice, IOUAmount{7071067811865480, -10}));
+
793 else
+
794 BEAST_EXPECT(amm.expectLPTokens(
+
795 alice, IOUAmount{7071067811865474, -10}));
+
796 BEAST_EXPECT(
+
797 amm.expectLPTokens(bob, IOUAmount{1414213562373095, -9}));
+
798
+
799 // gw clawback 10 USD from bob in amm.
+
800 env(amm::ammClawback(gw, bob, USD, XRP, USD(10)), ter(tesSUCCESS));
+
801 env.close();
+
802
+
803 env.require(balance(alice, gw["USD"](5000)));
+
804 env.require(balance(bob, gw["USD"](4000)));
+
805
+
806 // Bob gets 20 XRP back.
+
807 BEAST_EXPECT(
+
808 expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(20)));
+
809 if (!features[fixAMMv1_3])
+
810 BEAST_EXPECT(amm.expectBalances(
+
811 STAmount{USD, UINT64_C(2490000000000001), -12},
+
812 XRP(4980),
+
813 IOUAmount{3521391770309008, -9}));
+
814 else
+
815 BEAST_EXPECT(amm.expectBalances(
+
816 USD(2'490), XRP(4980), IOUAmount{3521391770309006, -9}));
+
817 if (!features[fixAMMv1_3])
+
818 BEAST_EXPECT(amm.expectLPTokens(
+
819 alice, IOUAmount{7071067811865480, -10}));
+
820 else
+
821 BEAST_EXPECT(amm.expectLPTokens(
+
822 alice, IOUAmount{7071067811865474, -10}));
+
823 if (!features[fixAMMv1_3])
+
824 BEAST_EXPECT(
+
825 amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
+
826 else
+
827 BEAST_EXPECT(
+
828 amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9}));
829
-
830 // Now neither alice nor bob has any lptoken in amm.
-
831 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
-
832 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
-
833
-
834 // gw2 claw back 1000 EUR from alice in amm2, which exceeds alice's
-
835 // balance. All alice's lptokens will be consumed, which corresponds
-
836 // to 800EUR / 2400 XRP.
-
837 env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(1000)),
-
838 ter(tesSUCCESS));
-
839 env.close();
-
840
-
841 env.require(balance(alice, gw2["EUR"](4000)));
-
842 env.require(balance(bob, gw2["EUR"](3000)));
-
843
-
844 // Alice gets another 2400 XRP back, bob's XRP balance remains the
-
845 // same.
-
846 BEAST_EXPECT(expectLedgerEntryRoot(
-
847 env,
-
848 alice,
-
849 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
-
850 XRP(2400)));
-
851 BEAST_EXPECT(expectLedgerEntryRoot(
-
852 env, bob, bobXrpBalance + XRP(20) + XRP(1980)));
-
853
-
854 // Alice now does not have any lptoken in amm2
-
855 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
-
856
-
857 BEAST_EXPECT(amm2.expectBalances(
-
858 EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
+
830 // gw2 clawback 200 EUR from amm2.
+
831 env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(200)),
+
832 ter(tesSUCCESS));
+
833 env.close();
+
834
+
835 env.require(balance(alice, gw2["EUR"](4000)));
+
836 env.require(balance(bob, gw2["EUR"](3000)));
+
837
+
838 // Alice gets 600 XRP back.
+
839 BEAST_EXPECT(expectLedgerEntryRoot(
+
840 env, alice, aliceXrpBalance + XRP(1000) + XRP(600)));
+
841 if (!features[fixAMMv1_3])
+
842 BEAST_EXPECT(amm2.expectBalances(
+
843 EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9}));
+
844 else
+
845 BEAST_EXPECT(amm2.expectBalances(
+
846 EUR(2800), XRP(8400), IOUAmount{4849742261192856, -9}));
+
847 if (!features[fixAMMv1_3])
+
848 BEAST_EXPECT(amm2.expectLPTokens(
+
849 alice, IOUAmount{1385640646055103, -9}));
+
850 else
+
851 BEAST_EXPECT(amm2.expectLPTokens(
+
852 alice, IOUAmount{1385640646055102, -9}));
+
853 if (!features[fixAMMv1_3])
+
854 BEAST_EXPECT(
+
855 amm2.expectLPTokens(bob, IOUAmount{1732050807568878, -9}));
+
856 else
+
857 BEAST_EXPECT(
+
858 amm2.expectLPTokens(bob, IOUAmount{1732050807568877, -9}));
859
-
860 // gw2 claw back 2000 EUR from bib in amm2, which exceeds bob's
-
861 // balance. All bob's lptokens will be consumed, which corresponds
-
862 // to 1000EUR / 3000 XRP.
-
863 env(amm::ammClawback(gw2, bob, EUR, XRP, EUR(2000)),
+
860 // gw claw back 1000 USD from alice in amm, which exceeds alice's
+
861 // balance. This will clawback all the remaining LP tokens of alice
+
862 // (corresponding 500 USD / 1000 XRP).
+
863 env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
864 ter(tesSUCCESS));
865 env.close();
866
-
867 env.require(balance(alice, gw2["EUR"](4000)));
-
868 env.require(balance(bob, gw2["EUR"](3000)));
+
867 env.require(balance(alice, gw["USD"](5000)));
+
868 env.require(balance(bob, gw["USD"](4000)));
869
-
870 // Bob gets another 3000 XRP back. Alice's XRP balance remains the
-
871 // same.
-
872 BEAST_EXPECT(expectLedgerEntryRoot(
-
873 env,
-
874 alice,
-
875 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
-
876 XRP(2400)));
-
877 BEAST_EXPECT(expectLedgerEntryRoot(
-
878 env, bob, bobXrpBalance + XRP(20) + XRP(1980) + XRP(3000)));
-
879
-
880 // Neither alice nor bob has any lptoken in amm2
-
881 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
-
882 BEAST_EXPECT(amm2.expectLPTokens(bob, IOUAmount(0)));
-
883
-
884 BEAST_EXPECT(amm2.expectBalances(
-
885 EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
-
886 }
-
887 }
-
888
-
889 void
-
890 testAMMClawbackAll(FeatureBitset features)
-
891 {
-
892 testcase("test AMMClawback all the tokens in the AMM pool");
-
893 using namespace jtx;
-
894
-
895 // Test AMMClawback for USD/EUR pool. The assets are issued by different
-
896 // issuer. Claw back all the USD for different users.
-
897 {
-
898 Env env(*this, features);
-
899 Account gw{"gateway"};
-
900 Account gw2{"gateway2"};
-
901 Account alice{"alice"};
-
902 Account bob{"bob"};
-
903 Account carol{"carol"};
-
904 env.fund(XRP(1000000), gw, gw2, alice, bob, carol);
+
870 // Alice gets 1000 XRP back.
+
871 if (!features[fixAMMv1_3])
+
872 BEAST_EXPECT(expectLedgerEntryRoot(
+
873 env,
+
874 alice,
+
875 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
+
876 else
+
877 BEAST_EXPECT(expectLedgerEntryRoot(
+
878 env,
+
879 alice,
+
880 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) -
+
881 XRPAmount{1}));
+
882 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+
883 if (!features[fixAMMv1_3])
+
884 BEAST_EXPECT(
+
885 amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
+
886 else
+
887 BEAST_EXPECT(
+
888 amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9}));
+
889 if (!features[fixAMMv1_3])
+
890 BEAST_EXPECT(amm.expectBalances(
+
891 STAmount{USD, UINT64_C(1990000000000001), -12},
+
892 XRP(3980),
+
893 IOUAmount{2814284989122460, -9}));
+
894 else
+
895 BEAST_EXPECT(amm.expectBalances(
+
896 USD(1'990),
+
897 XRPAmount{3'980'000'001},
+
898 IOUAmount{2814284989122459, -9}));
+
899
+
900 // gw clawback 1000 USD from bob in amm, which also exceeds bob's
+
901 // balance in amm. All bob's lptoken in amm will be consumed, which
+
902 // corresponds to 990 USD / 1980 XRP
+
903 env(amm::ammClawback(gw, bob, USD, XRP, USD(1000)),
+
904 ter(tesSUCCESS));
905 env.close();
906
-
907 // gw sets asfAllowTrustLineClawback.
-
908 env(fset(gw, asfAllowTrustLineClawback));
-
909 env.close();
-
910 env.require(flags(gw, asfAllowTrustLineClawback));
-
911
-
912 // gw2 sets asfAllowTrustLineClawback.
-
913 env(fset(gw2, asfAllowTrustLineClawback));
-
914 env.close();
-
915 env.require(flags(gw2, asfAllowTrustLineClawback));
-
916
-
917 // gw issues 6000 USD to Alice, 5000 USD to Bob, and 4000 USD
-
918 // to Carol.
-
919 auto const USD = gw["USD"];
-
920 env.trust(USD(100000), alice);
-
921 env(pay(gw, alice, USD(6000)));
-
922 env.trust(USD(100000), bob);
-
923 env(pay(gw, bob, USD(5000)));
-
924 env.trust(USD(100000), carol);
-
925 env(pay(gw, carol, USD(4000)));
-
926 env.close();
+
907 env.require(balance(alice, gw["USD"](5000)));
+
908 env.require(balance(bob, gw["USD"](4000)));
+
909
+
910 if (!features[fixAMMv1_3])
+
911 BEAST_EXPECT(expectLedgerEntryRoot(
+
912 env,
+
913 alice,
+
914 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
+
915 else
+
916 BEAST_EXPECT(expectLedgerEntryRoot(
+
917 env,
+
918 alice,
+
919 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) -
+
920 XRPAmount{1}));
+
921 BEAST_EXPECT(expectLedgerEntryRoot(
+
922 env, bob, bobXrpBalance + XRP(20) + XRP(1980)));
+
923
+
924 // Now neither alice nor bob has any lptoken in amm.
+
925 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+
926 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
927
-
928 // gw2 issues 6000 EUR to Alice and 5000 EUR to Bob and 4000
-
929 // EUR to Carol.
-
930 auto const EUR = gw2["EUR"];
-
931 env.trust(EUR(100000), alice);
-
932 env(pay(gw2, alice, EUR(6000)));
-
933 env.trust(EUR(100000), bob);
-
934 env(pay(gw2, bob, EUR(5000)));
-
935 env.trust(EUR(100000), carol);
-
936 env(pay(gw2, carol, EUR(4000)));
-
937 env.close();
-
938
-
939 // Alice creates AMM pool of EUR/USD
-
940 AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
-
941 env.close();
-
942
-
943 BEAST_EXPECT(amm.expectBalances(
-
944 USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
-
945 amm.deposit(bob, USD(2000), EUR(2500));
-
946 BEAST_EXPECT(amm.expectBalances(
-
947 USD(6000), EUR(7500), IOUAmount{6708203932499370, -12}));
-
948 amm.deposit(carol, USD(1000), EUR(1250));
-
949 BEAST_EXPECT(amm.expectBalances(
-
950 USD(7000), EUR(8750), IOUAmount{7826237921249265, -12}));
-
951
-
952 BEAST_EXPECT(
-
953 amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12}));
-
954 BEAST_EXPECT(
-
955 amm.expectLPTokens(bob, IOUAmount{2236067977499790, -12}));
-
956 BEAST_EXPECT(
-
957 amm.expectLPTokens(carol, IOUAmount{1118033988749895, -12}));
-
958
-
959 env.require(balance(alice, gw["USD"](2000)));
-
960 env.require(balance(alice, gw2["EUR"](1000)));
-
961 env.require(balance(bob, gw["USD"](3000)));
-
962 env.require(balance(bob, gw2["EUR"](2500)));
-
963 env.require(balance(carol, gw["USD"](3000)));
-
964 env.require(balance(carol, gw2["EUR"](2750)));
-
965
-
966 // gw clawback all the bob's USD in amm. (2000 USD / 2500 EUR)
-
967 env(amm::ammClawback(gw, bob, USD, EUR, std::nullopt),
-
968 ter(tesSUCCESS));
-
969 env.close();
-
970
-
971 BEAST_EXPECT(amm.expectBalances(
-
972 STAmount{USD, UINT64_C(4999999999999999), -12},
-
973 STAmount{EUR, UINT64_C(6249999999999999), -12},
-
974 IOUAmount{5590169943749475, -12}));
-
975
-
976 BEAST_EXPECT(
-
977 amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12}));
-
978 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
-
979 BEAST_EXPECT(
-
980 amm.expectLPTokens(carol, IOUAmount{1118033988749895, -12}));
-
981
-
982 // Bob will get 2500 EUR back.
-
983 env.require(balance(alice, gw["USD"](2000)));
-
984 env.require(balance(alice, gw2["EUR"](1000)));
-
985 BEAST_EXPECT(
-
986 env.balance(bob, USD) ==
-
987 STAmount(USD, UINT64_C(3000000000000000), -12));
-
988
-
989 BEAST_EXPECT(
-
990 env.balance(bob, EUR) ==
-
991 STAmount(EUR, UINT64_C(5000000000000001), -12));
-
992 env.require(balance(carol, gw["USD"](3000)));
-
993 env.require(balance(carol, gw2["EUR"](2750)));
-
994
-
995 // gw2 clawback all carol's EUR in amm. (1000 USD / 1250 EUR)
-
996 env(amm::ammClawback(gw2, carol, EUR, USD, std::nullopt),
-
997 ter(tesSUCCESS));
-
998 env.close();
-
999 BEAST_EXPECT(amm.expectBalances(
-
1000 STAmount{USD, UINT64_C(3999999999999999), -12},
-
1001 STAmount{EUR, UINT64_C(4999999999999999), -12},
-
1002 IOUAmount{4472135954999580, -12}));
-
1003
-
1004 BEAST_EXPECT(
-
1005 amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12}));
-
1006 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
-
1007 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(0)));
-
1008
-
1009 // gw2 clawback all alice's EUR in amm. (4000 USD / 5000 EUR)
-
1010 env(amm::ammClawback(gw2, alice, EUR, USD, std::nullopt),
-
1011 ter(tesSUCCESS));
-
1012 env.close();
-
1013
-
1014 env.require(balance(carol, gw2["EUR"](2750)));
-
1015 env.require(balance(carol, gw["USD"](4000)));
-
1016 BEAST_EXPECT(!amm.ammExists());
-
1017 }
-
1018
-
1019 // Test AMMClawback for USD/XRP pool. Claw back all the USD for
-
1020 // different users.
-
1021 {
-
1022 Env env(*this, features);
-
1023 Account gw{"gateway"};
-
1024 Account alice{"alice"};
-
1025 Account bob{"bob"};
-
1026 env.fund(XRP(1000000), gw, alice, bob);
-
1027 env.close();
-
1028
-
1029 // gw sets asfAllowTrustLineClawback
-
1030 env(fset(gw, asfAllowTrustLineClawback));
-
1031 env.close();
-
1032 env.require(flags(gw, asfAllowTrustLineClawback));
-
1033
-
1034 // gw issues 600000 USD to Alice and 500000 USD to Bob.
+
928 // gw2 claw back 1000 EUR from alice in amm2, which exceeds alice's
+
929 // balance. All alice's lptokens will be consumed, which corresponds
+
930 // to 800EUR / 2400 XRP.
+
931 env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(1000)),
+
932 ter(tesSUCCESS));
+
933 env.close();
+
934
+
935 env.require(balance(alice, gw2["EUR"](4000)));
+
936 env.require(balance(bob, gw2["EUR"](3000)));
+
937
+
938 // Alice gets another 2400 XRP back, bob's XRP balance remains the
+
939 // same.
+
940 if (!features[fixAMMv1_3])
+
941 BEAST_EXPECT(expectLedgerEntryRoot(
+
942 env,
+
943 alice,
+
944 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
+
945 XRP(2400)));
+
946 else
+
947 BEAST_EXPECT(expectLedgerEntryRoot(
+
948 env,
+
949 alice,
+
950 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
+
951 XRP(2400) - XRPAmount{1}));
+
952 BEAST_EXPECT(expectLedgerEntryRoot(
+
953 env, bob, bobXrpBalance + XRP(20) + XRP(1980)));
+
954
+
955 // Alice now does not have any lptoken in amm2
+
956 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
+
957
+
958 if (!features[fixAMMv1_3])
+
959 BEAST_EXPECT(amm2.expectBalances(
+
960 EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
+
961 else
+
962 BEAST_EXPECT(amm2.expectBalances(
+
963 EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9}));
+
964
+
965 // gw2 claw back 2000 EUR from bob in amm2, which exceeds bob's
+
966 // balance. All bob's lptokens will be consumed, which corresponds
+
967 // to 1000EUR / 3000 XRP.
+
968 env(amm::ammClawback(gw2, bob, EUR, XRP, EUR(2000)),
+
969 ter(tesSUCCESS));
+
970 env.close();
+
971
+
972 env.require(balance(alice, gw2["EUR"](4000)));
+
973 env.require(balance(bob, gw2["EUR"](3000)));
+
974
+
975 // Bob gets another 3000 XRP back. Alice's XRP balance remains the
+
976 // same.
+
977 if (!features[fixAMMv1_3])
+
978 BEAST_EXPECT(expectLedgerEntryRoot(
+
979 env,
+
980 alice,
+
981 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
+
982 XRP(2400)));
+
983 else
+
984 BEAST_EXPECT(expectLedgerEntryRoot(
+
985 env,
+
986 alice,
+
987 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
+
988 XRP(2400) - XRPAmount{1}));
+
989 BEAST_EXPECT(expectLedgerEntryRoot(
+
990 env, bob, bobXrpBalance + XRP(20) + XRP(1980) + XRP(3000)));
+
991
+
992 // Neither alice nor bob has any lptoken in amm2
+
993 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
+
994 BEAST_EXPECT(amm2.expectLPTokens(bob, IOUAmount(0)));
+
995
+
996 if (!features[fixAMMv1_3])
+
997 BEAST_EXPECT(amm2.expectBalances(
+
998 EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
+
999 else
+
1000 BEAST_EXPECT(amm2.expectBalances(
+
1001 EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9}));
+
1002 }
+
1003 }
+
1004
+
1005 void
+
1006 testAMMClawbackAll(FeatureBitset features)
+
1007 {
+
1008 testcase("test AMMClawback all the tokens in the AMM pool");
+
1009 using namespace jtx;
+
1010
+
1011 // Test AMMClawback for USD/EUR pool. The assets are issued by different
+
1012 // issuer. Claw back all the USD for different users.
+
1013 {
+
1014 Env env(*this, features);
+
1015 Account gw{"gateway"};
+
1016 Account gw2{"gateway2"};
+
1017 Account alice{"alice"};
+
1018 Account bob{"bob"};
+
1019 Account carol{"carol"};
+
1020 env.fund(XRP(1000000), gw, gw2, alice, bob, carol);
+
1021 env.close();
+
1022
+
1023 // gw sets asfAllowTrustLineClawback.
+
1024 env(fset(gw, asfAllowTrustLineClawback));
+
1025 env.close();
+
1026 env.require(flags(gw, asfAllowTrustLineClawback));
+
1027
+
1028 // gw2 sets asfAllowTrustLineClawback.
+
1029 env(fset(gw2, asfAllowTrustLineClawback));
+
1030 env.close();
+
1031 env.require(flags(gw2, asfAllowTrustLineClawback));
+
1032
+
1033 // gw issues 6000 USD to Alice, 5000 USD to Bob, and 4000 USD
+
1034 // to Carol.
1035 auto const USD = gw["USD"];
-
1036 env.trust(USD(1000000), alice);
-
1037 env(pay(gw, alice, USD(600000)));
-
1038 env.trust(USD(1000000), bob);
-
1039 env(pay(gw, bob, USD(500000)));
-
1040 env.close();
-
1041
-
1042 // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD.
-
1043 AMM amm(env, gw, XRP(2000), USD(10000), ter(tesSUCCESS));
-
1044 BEAST_EXPECT(amm.expectBalances(
-
1045 USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
-
1046 amm.deposit(alice, USD(1000), XRP(200));
-
1047 BEAST_EXPECT(amm.expectBalances(
-
1048 USD(11000), XRP(2200), IOUAmount{4919349550499538, -9}));
-
1049 amm.deposit(bob, USD(2000), XRP(400));
-
1050 BEAST_EXPECT(amm.expectBalances(
-
1051 USD(13000), XRP(2600), IOUAmount{5813776741499453, -9}));
-
1052 env.close();
-
1053
-
1054 auto aliceXrpBalance = env.balance(alice, XRP);
-
1055 auto bobXrpBalance = env.balance(bob, XRP);
-
1056
-
1057 // gw clawback all alice's USD in amm. (1000 USD / 200 XRP)
-
1058 env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
-
1059 ter(tesSUCCESS));
-
1060 env.close();
-
1061 BEAST_EXPECT(amm.expectBalances(
-
1062 USD(12000), XRP(2400), IOUAmount{5366563145999495, -9}));
-
1063 BEAST_EXPECT(
-
1064 expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(200)));
-
1065 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
-
1066
-
1067 // gw clawback all bob's USD in amm. (2000 USD / 400 XRP)
-
1068 env(amm::ammClawback(gw, bob, USD, XRP, std::nullopt),
-
1069 ter(tesSUCCESS));
-
1070 env.close();
-
1071 BEAST_EXPECT(amm.expectBalances(
-
1072 USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
-
1073 BEAST_EXPECT(
-
1074 expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(400)));
-
1075 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
-
1076 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
-
1077 }
-
1078 }
+
1036 env.trust(USD(100000), alice);
+
1037 env(pay(gw, alice, USD(6000)));
+
1038 env.trust(USD(100000), bob);
+
1039 env(pay(gw, bob, USD(5000)));
+
1040 env.trust(USD(100000), carol);
+
1041 env(pay(gw, carol, USD(4000)));
+
1042 env.close();
+
1043
+
1044 // gw2 issues 6000 EUR to Alice and 5000 EUR to Bob and 4000
+
1045 // EUR to Carol.
+
1046 auto const EUR = gw2["EUR"];
+
1047 env.trust(EUR(100000), alice);
+
1048 env(pay(gw2, alice, EUR(6000)));
+
1049 env.trust(EUR(100000), bob);
+
1050 env(pay(gw2, bob, EUR(5000)));
+
1051 env.trust(EUR(100000), carol);
+
1052 env(pay(gw2, carol, EUR(4000)));
+
1053 env.close();
+
1054
+
1055 // Alice creates AMM pool of EUR/USD
+
1056 AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
+
1057 env.close();
+
1058
+
1059 if (!features[fixAMMv1_3])
+
1060 BEAST_EXPECT(amm.expectBalances(
+
1061 USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
+
1062 else
+
1063 BEAST_EXPECT(amm.expectBalances(
+
1064 USD(4000), EUR(5000), IOUAmount{4472135954999579, -12}));
+
1065 amm.deposit(bob, USD(2000), EUR(2500));
+
1066 if (!features[fixAMMv1_3])
+
1067 BEAST_EXPECT(amm.expectBalances(
+
1068 USD(6000), EUR(7500), IOUAmount{6708203932499370, -12}));
+
1069 else
+
1070 BEAST_EXPECT(amm.expectBalances(
+
1071 USD(6000), EUR(7500), IOUAmount{6708203932499368, -12}));
+
1072 amm.deposit(carol, USD(1000), EUR(1250));
+
1073 if (!features[fixAMMv1_3])
+
1074 BEAST_EXPECT(amm.expectBalances(
+
1075 USD(7000), EUR(8750), IOUAmount{7826237921249265, -12}));
+
1076 else
+
1077 BEAST_EXPECT(amm.expectBalances(
+
1078 USD(7000), EUR(8750), IOUAmount{7826237921249262, -12}));
1079
-
1080 void
-
1081 testAMMClawbackSameIssuerAssets(FeatureBitset features)
-
1082 {
-
1083 testcase(
-
1084 "test AMMClawback from AMM pool with assets having the same "
-
1085 "issuer");
-
1086 using namespace jtx;
-
1087
-
1088 // Test AMMClawback for USD/EUR pool. The assets are issued by different
-
1089 // issuer. Claw back all the USD for different users.
-
1090 Env env(*this, features);
-
1091 Account gw{"gateway"};
-
1092 Account alice{"alice"};
-
1093 Account bob{"bob"};
-
1094 Account carol{"carol"};
-
1095 env.fund(XRP(1000000), gw, alice, bob, carol);
-
1096 env.close();
-
1097
-
1098 // gw sets asfAllowTrustLineClawback.
-
1099 env(fset(gw, asfAllowTrustLineClawback));
-
1100 env.close();
-
1101 env.require(flags(gw, asfAllowTrustLineClawback));
-
1102
-
1103 auto const USD = gw["USD"];
-
1104 env.trust(USD(100000), alice);
-
1105 env(pay(gw, alice, USD(10000)));
-
1106 env.trust(USD(100000), bob);
-
1107 env(pay(gw, bob, USD(9000)));
-
1108 env.trust(USD(100000), carol);
-
1109 env(pay(gw, carol, USD(8000)));
-
1110 env.close();
-
1111
-
1112 auto const EUR = gw["EUR"];
-
1113 env.trust(EUR(100000), alice);
-
1114 env(pay(gw, alice, EUR(10000)));
-
1115 env.trust(EUR(100000), bob);
-
1116 env(pay(gw, bob, EUR(9000)));
-
1117 env.trust(EUR(100000), carol);
-
1118 env(pay(gw, carol, EUR(8000)));
-
1119 env.close();
-
1120
-
1121 AMM amm(env, alice, EUR(2000), USD(8000), ter(tesSUCCESS));
-
1122 env.close();
-
1123
-
1124 BEAST_EXPECT(amm.expectBalances(USD(8000), EUR(2000), IOUAmount(4000)));
-
1125 amm.deposit(bob, USD(4000), EUR(1000));
-
1126 BEAST_EXPECT(
-
1127 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
-
1128 amm.deposit(carol, USD(2000), EUR(500));
-
1129 BEAST_EXPECT(
-
1130 amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
-
1131
-
1132 // gw clawback 1000 USD from carol.
-
1133 env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)), ter(tesSUCCESS));
-
1134 env.close();
-
1135 BEAST_EXPECT(
-
1136 amm.expectBalances(USD(13000), EUR(3250), IOUAmount(6500)));
-
1137
-
1138 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
-
1139 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2000)));
-
1140 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
-
1141 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
-
1142 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
-
1143 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
-
1144 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
-
1145 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
-
1146 // 250 EUR goes back to carol.
-
1147 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
-
1148
-
1149 // gw clawback 1000 USD from bob with tfClawTwoAssets flag.
-
1150 // then the corresponding EUR will also be clawed back
-
1151 // by gw.
-
1152 env(amm::ammClawback(gw, bob, USD, EUR, USD(1000)),
-
1153 txflags(tfClawTwoAssets),
-
1154 ter(tesSUCCESS));
-
1155 env.close();
-
1156 BEAST_EXPECT(
-
1157 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
-
1158
-
1159 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
-
1160 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
-
1161 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
-
1162 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
-
1163 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
-
1164 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
-
1165 // 250 EUR did not go back to bob because tfClawTwoAssets is set.
-
1166 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
-
1167 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
-
1168 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
-
1169
-
1170 // gw clawback all USD from alice and set tfClawTwoAssets.
-
1171 env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
-
1172 txflags(tfClawTwoAssets),
-
1173 ter(tesSUCCESS));
-
1174 env.close();
-
1175 BEAST_EXPECT(amm.expectBalances(USD(4000), EUR(1000), IOUAmount(2000)));
-
1176
-
1177 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
-
1178 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
-
1179 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
-
1180 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
-
1181 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
-
1182 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
-
1183 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
-
1184 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
-
1185 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
-
1186 }
+
1080 if (!features[fixAMMv1_3])
+
1081 BEAST_EXPECT(amm.expectLPTokens(
+
1082 alice, IOUAmount{4472135954999580, -12}));
+
1083 else
+
1084 BEAST_EXPECT(amm.expectLPTokens(
+
1085 alice, IOUAmount{4472135954999579, -12}));
+
1086 if (!features[fixAMMv1_3])
+
1087 BEAST_EXPECT(
+
1088 amm.expectLPTokens(bob, IOUAmount{2236067977499790, -12}));
+
1089 else
+
1090 BEAST_EXPECT(
+
1091 amm.expectLPTokens(bob, IOUAmount{2236067977499789, -12}));
+
1092 if (!features[fixAMMv1_3])
+
1093 BEAST_EXPECT(amm.expectLPTokens(
+
1094 carol, IOUAmount{1118033988749895, -12}));
+
1095 else
+
1096 BEAST_EXPECT(amm.expectLPTokens(
+
1097 carol, IOUAmount{1118033988749894, -12}));
+
1098
+
1099 env.require(balance(alice, gw["USD"](2000)));
+
1100 env.require(balance(alice, gw2["EUR"](1000)));
+
1101 env.require(balance(bob, gw["USD"](3000)));
+
1102 env.require(balance(bob, gw2["EUR"](2500)));
+
1103 env.require(balance(carol, gw["USD"](3000)));
+
1104 env.require(balance(carol, gw2["EUR"](2750)));
+
1105
+
1106 // gw clawback all the bob's USD in amm. (2000 USD / 2500 EUR)
+
1107 env(amm::ammClawback(gw, bob, USD, EUR, std::nullopt),
+
1108 ter(tesSUCCESS));
+
1109 env.close();
+
1110
+
1111 if (!features[fixAMMv1_3])
+
1112 BEAST_EXPECT(amm.expectBalances(
+
1113 STAmount{USD, UINT64_C(4999999999999999), -12},
+
1114 STAmount{EUR, UINT64_C(6249999999999999), -12},
+
1115 IOUAmount{5590169943749475, -12}));
+
1116 else
+
1117 BEAST_EXPECT(amm.expectBalances(
+
1118 STAmount{USD, UINT64_C(5000000000000001), -12},
+
1119 STAmount{EUR, UINT64_C(6250000000000001), -12},
+
1120 IOUAmount{5590169943749473, -12}));
+
1121
+
1122 if (!features[fixAMMv1_3])
+
1123 BEAST_EXPECT(amm.expectLPTokens(
+
1124 alice, IOUAmount{4472135954999580, -12}));
+
1125 else
+
1126 BEAST_EXPECT(amm.expectLPTokens(
+
1127 alice, IOUAmount{4472135954999579, -12}));
+
1128 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
+
1129 if (!features[fixAMMv1_3])
+
1130 BEAST_EXPECT(amm.expectLPTokens(
+
1131 carol, IOUAmount{1118033988749895, -12}));
+
1132 else
+
1133 BEAST_EXPECT(amm.expectLPTokens(
+
1134 carol, IOUAmount{1118033988749894, -12}));
+
1135
+
1136 // Bob will get 2500 EUR back.
+
1137 env.require(balance(alice, gw["USD"](2000)));
+
1138 env.require(balance(alice, gw2["EUR"](1000)));
+
1139 BEAST_EXPECT(
+
1140 env.balance(bob, USD) ==
+
1141 STAmount(USD, UINT64_C(3000000000000000), -12));
+
1142
+
1143 if (!features[fixAMMv1_3])
+
1144 BEAST_EXPECT(
+
1145 env.balance(bob, EUR) ==
+
1146 STAmount(EUR, UINT64_C(5000000000000001), -12));
+
1147 else
+
1148 BEAST_EXPECT(
+
1149 env.balance(bob, EUR) ==
+
1150 STAmount(EUR, UINT64_C(4999999999999999), -12));
+
1151 env.require(balance(carol, gw["USD"](3000)));
+
1152 env.require(balance(carol, gw2["EUR"](2750)));
+
1153
+
1154 // gw2 clawback all carol's EUR in amm. (1000 USD / 1250 EUR)
+
1155 env(amm::ammClawback(gw2, carol, EUR, USD, std::nullopt),
+
1156 ter(tesSUCCESS));
+
1157 env.close();
+
1158 if (!features[fixAMMv1_3])
+
1159 BEAST_EXPECT(amm.expectBalances(
+
1160 STAmount{USD, UINT64_C(3999999999999999), -12},
+
1161 STAmount{EUR, UINT64_C(4999999999999999), -12},
+
1162 IOUAmount{4472135954999580, -12}));
+
1163 else
+
1164 BEAST_EXPECT(amm.expectBalances(
+
1165 STAmount{USD, UINT64_C(4000000000000001), -12},
+
1166 STAmount{EUR, UINT64_C(5000000000000002), -12},
+
1167 IOUAmount{4472135954999579, -12}));
+
1168
+
1169 if (!features[fixAMMv1_3])
+
1170 BEAST_EXPECT(amm.expectLPTokens(
+
1171 alice, IOUAmount{4472135954999580, -12}));
+
1172 else
+
1173 BEAST_EXPECT(amm.expectLPTokens(
+
1174 alice, IOUAmount{4472135954999579, -12}));
+
1175 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
+
1176 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(0)));
+
1177
+
1178 // gw2 clawback all alice's EUR in amm. (4000 USD / 5000 EUR)
+
1179 env(amm::ammClawback(gw2, alice, EUR, USD, std::nullopt),
+
1180 ter(tesSUCCESS));
+
1181 env.close();
+
1182
+
1183 env.require(balance(carol, gw2["EUR"](2750)));
+
1184 env.require(balance(carol, gw["USD"](4000)));
+
1185 BEAST_EXPECT(!amm.ammExists());
+
1186 }
1187
-
1188 void
-
1189 testAMMClawbackSameCurrency(FeatureBitset features)
-
1190 {
-
1191 testcase(
-
1192 "test AMMClawback from AMM pool with assets having the same "
-
1193 "currency, but from different issuer");
-
1194 using namespace jtx;
-
1195
-
1196 // Test AMMClawback for USD/EUR pool. The assets are issued by different
-
1197 // issuer. Claw back all the USD for different users.
-
1198 Env env(*this, features);
-
1199 Account gw{"gateway"};
-
1200 Account gw2{"gateway2"};
-
1201 Account alice{"alice"};
-
1202 Account bob{"bob"};
-
1203 env.fund(XRP(1000000), gw, gw2, alice, bob);
-
1204 env.close();
-
1205
-
1206 // gw sets asfAllowTrustLineClawback.
-
1207 env(fset(gw, asfAllowTrustLineClawback));
-
1208 env.close();
-
1209 env.require(flags(gw, asfAllowTrustLineClawback));
+
1188 // Test AMMClawback for USD/XRP pool. Claw back all the USD for
+
1189 // different users.
+
1190 {
+
1191 Env env(*this, features);
+
1192 Account gw{"gateway"};
+
1193 Account alice{"alice"};
+
1194 Account bob{"bob"};
+
1195 env.fund(XRP(1000000), gw, alice, bob);
+
1196 env.close();
+
1197
+
1198 // gw sets asfAllowTrustLineClawback
+
1199 env(fset(gw, asfAllowTrustLineClawback));
+
1200 env.close();
+
1201 env.require(flags(gw, asfAllowTrustLineClawback));
+
1202
+
1203 // gw issues 600000 USD to Alice and 500000 USD to Bob.
+
1204 auto const USD = gw["USD"];
+
1205 env.trust(USD(1000000), alice);
+
1206 env(pay(gw, alice, USD(600000)));
+
1207 env.trust(USD(1000000), bob);
+
1208 env(pay(gw, bob, USD(500000)));
+
1209 env.close();
1210
-
1211 // gw2 sets asfAllowTrustLineClawback.
-
1212 env(fset(gw2, asfAllowTrustLineClawback));
-
1213 env.close();
-
1214 env.require(flags(gw2, asfAllowTrustLineClawback));
-
1215
-
1216 env.trust(gw["USD"](100000), alice);
-
1217 env(pay(gw, alice, gw["USD"](8000)));
-
1218 env.trust(gw["USD"](100000), bob);
-
1219 env(pay(gw, bob, gw["USD"](7000)));
-
1220
-
1221 env.trust(gw2["USD"](100000), alice);
-
1222 env(pay(gw2, alice, gw2["USD"](6000)));
-
1223 env.trust(gw2["USD"](100000), bob);
-
1224 env(pay(gw2, bob, gw2["USD"](5000)));
-
1225 env.close();
-
1226
-
1227 AMM amm(env, alice, gw["USD"](1000), gw2["USD"](1500), ter(tesSUCCESS));
-
1228 env.close();
-
1229
-
1230 BEAST_EXPECT(amm.expectBalances(
-
1231 gw["USD"](1000),
-
1232 gw2["USD"](1500),
-
1233 IOUAmount{1224744871391589, -12}));
-
1234 amm.deposit(bob, gw["USD"](2000), gw2["USD"](3000));
-
1235 BEAST_EXPECT(amm.expectBalances(
-
1236 gw["USD"](3000),
-
1237 gw2["USD"](4500),
-
1238 IOUAmount{3674234614174767, -12}));
-
1239
-
1240 // Issuer does not match with asset.
-
1241 env(amm::ammClawback(
-
1242 gw,
-
1243 alice,
-
1244 gw2["USD"],
-
1245 gw["USD"],
-
1246 STAmount{Issue{gw2["USD"].currency, gw2.id()}, 500}),
-
1247 ter(temMALFORMED));
-
1248
-
1249 // gw2 clawback 500 gw2[USD] from alice.
-
1250 env(amm::ammClawback(
-
1251 gw2,
-
1252 alice,
-
1253 gw2["USD"],
-
1254 gw["USD"],
-
1255 STAmount{Issue{gw2["USD"].currency, gw2.id()}, 500}),
-
1256 ter(tesSUCCESS));
-
1257 env.close();
-
1258 BEAST_EXPECT(amm.expectBalances(
-
1259 STAmount{gw["USD"], UINT64_C(2666666666666667), -12},
-
1260 gw2["USD"](4000),
-
1261 IOUAmount{3265986323710904, -12}));
-
1262
-
1263 BEAST_EXPECT(
-
1264 amm.expectLPTokens(alice, IOUAmount{8164965809277260, -13}));
-
1265 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2449489742783178, -12}));
-
1266 BEAST_EXPECT(
-
1267 env.balance(alice, gw["USD"]) ==
-
1268 STAmount(gw["USD"], UINT64_C(7333333333333333), -12));
-
1269 BEAST_EXPECT(env.balance(alice, gw2["USD"]) == gw2["USD"](4500));
-
1270 BEAST_EXPECT(env.balance(bob, gw["USD"]) == gw["USD"](5000));
-
1271 BEAST_EXPECT(env.balance(bob, gw2["USD"]) == gw2["USD"](2000));
-
1272
-
1273 // gw clawback all gw["USD"] from bob.
-
1274 env(amm::ammClawback(gw, bob, gw["USD"], gw2["USD"], std::nullopt),
-
1275 ter(tesSUCCESS));
-
1276 env.close();
-
1277 BEAST_EXPECT(amm.expectBalances(
-
1278 STAmount{gw["USD"], UINT64_C(6666666666666670), -13},
-
1279 gw2["USD"](1000),
-
1280 IOUAmount{8164965809277260, -13}));
-
1281
-
1282 BEAST_EXPECT(
-
1283 amm.expectLPTokens(alice, IOUAmount{8164965809277260, -13}));
-
1284 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
-
1285 BEAST_EXPECT(
-
1286 env.balance(alice, gw["USD"]) ==
-
1287 STAmount(gw["USD"], UINT64_C(7333333333333333), -12));
-
1288 BEAST_EXPECT(env.balance(alice, gw2["USD"]) == gw2["USD"](4500));
-
1289 BEAST_EXPECT(env.balance(bob, gw["USD"]) == gw["USD"](5000));
-
1290 // Bob gets 3000 gw2["USD"] back and now his balance is 5000.
-
1291 BEAST_EXPECT(env.balance(bob, gw2["USD"]) == gw2["USD"](5000));
-
1292 }
-
1293
-
1294 void
-
1295 testAMMClawbackIssuesEachOther(FeatureBitset features)
-
1296 {
-
1297 testcase("test AMMClawback when issuing token for each other");
-
1298 using namespace jtx;
+
1211 // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD.
+
1212 AMM amm(env, gw, XRP(2000), USD(10000), ter(tesSUCCESS));
+
1213 if (!features[fixAMMv1_3])
+
1214 BEAST_EXPECT(amm.expectBalances(
+
1215 USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
+
1216 else
+
1217 BEAST_EXPECT(amm.expectBalances(
+
1218 USD(10000), XRP(2000), IOUAmount{4472135954999579, -9}));
+
1219 amm.deposit(alice, USD(1000), XRP(200));
+
1220 if (!features[fixAMMv1_3])
+
1221 BEAST_EXPECT(amm.expectBalances(
+
1222 USD(11000), XRP(2200), IOUAmount{4919349550499538, -9}));
+
1223 else
+
1224 BEAST_EXPECT(amm.expectBalances(
+
1225 USD(11000), XRP(2200), IOUAmount{4919349550499536, -9}));
+
1226 amm.deposit(bob, USD(2000), XRP(400));
+
1227 if (!features[fixAMMv1_3])
+
1228 BEAST_EXPECT(amm.expectBalances(
+
1229 USD(13000), XRP(2600), IOUAmount{5813776741499453, -9}));
+
1230 else
+
1231 BEAST_EXPECT(amm.expectBalances(
+
1232 USD(13000), XRP(2600), IOUAmount{5813776741499451, -9}));
+
1233 env.close();
+
1234
+
1235 auto aliceXrpBalance = env.balance(alice, XRP);
+
1236 auto bobXrpBalance = env.balance(bob, XRP);
+
1237
+
1238 // gw clawback all alice's USD in amm. (1000 USD / 200 XRP)
+
1239 env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
+
1240 ter(tesSUCCESS));
+
1241 env.close();
+
1242 if (!features[fixAMMv1_3])
+
1243 BEAST_EXPECT(amm.expectBalances(
+
1244 USD(12000), XRP(2400), IOUAmount{5366563145999495, -9}));
+
1245 else
+
1246 BEAST_EXPECT(amm.expectBalances(
+
1247 USD(12000),
+
1248 XRPAmount(2400000001),
+
1249 IOUAmount{5366563145999494, -9}));
+
1250 if (!features[fixAMMv1_3])
+
1251 BEAST_EXPECT(expectLedgerEntryRoot(
+
1252 env, alice, aliceXrpBalance + XRP(200)));
+
1253 else
+
1254 BEAST_EXPECT(expectLedgerEntryRoot(
+
1255 env, alice, aliceXrpBalance + XRP(200) - XRPAmount{1}));
+
1256 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+
1257
+
1258 // gw clawback all bob's USD in amm. (2000 USD / 400 XRP)
+
1259 env(amm::ammClawback(gw, bob, USD, XRP, std::nullopt),
+
1260 ter(tesSUCCESS));
+
1261 env.close();
+
1262 if (!features[fixAMMv1_3])
+
1263 BEAST_EXPECT(amm.expectBalances(
+
1264 USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
+
1265 else
+
1266 BEAST_EXPECT(amm.expectBalances(
+
1267 USD(10000),
+
1268 XRPAmount(2000000001),
+
1269 IOUAmount{4472135954999579, -9}));
+
1270 BEAST_EXPECT(
+
1271 expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(400)));
+
1272 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+
1273 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
+
1274 }
+
1275 }
+
1276
+
1277 void
+
1278 testAMMClawbackSameIssuerAssets(FeatureBitset features)
+
1279 {
+
1280 testcase(
+
1281 "test AMMClawback from AMM pool with assets having the same "
+
1282 "issuer");
+
1283 using namespace jtx;
+
1284
+
1285 // Test AMMClawback for USD/EUR pool. The assets are issued by different
+
1286 // issuer. Claw back all the USD for different users.
+
1287 Env env(*this, features);
+
1288 Account gw{"gateway"};
+
1289 Account alice{"alice"};
+
1290 Account bob{"bob"};
+
1291 Account carol{"carol"};
+
1292 env.fund(XRP(1000000), gw, alice, bob, carol);
+
1293 env.close();
+
1294
+
1295 // gw sets asfAllowTrustLineClawback.
+
1296 env(fset(gw, asfAllowTrustLineClawback));
+
1297 env.close();
+
1298 env.require(flags(gw, asfAllowTrustLineClawback));
1299
-
1300 // gw and gw2 issues token for each other. Test AMMClawback from
-
1301 // each other.
-
1302 Env env(*this, features);
-
1303 Account gw{"gateway"};
-
1304 Account gw2{"gateway2"};
-
1305 Account alice{"alice"};
-
1306 env.fund(XRP(1000000), gw, gw2, alice);
+
1300 auto const USD = gw["USD"];
+
1301 env.trust(USD(100000), alice);
+
1302 env(pay(gw, alice, USD(10000)));
+
1303 env.trust(USD(100000), bob);
+
1304 env(pay(gw, bob, USD(9000)));
+
1305 env.trust(USD(100000), carol);
+
1306 env(pay(gw, carol, USD(8000)));
1307 env.close();
1308
-
1309 // gw sets asfAllowTrustLineClawback.
-
1310 env(fset(gw, asfAllowTrustLineClawback));
-
1311 env.close();
-
1312 env.require(flags(gw, asfAllowTrustLineClawback));
-
1313
-
1314 // gw2 sets asfAllowTrustLineClawback.
-
1315 env(fset(gw2, asfAllowTrustLineClawback));
+
1309 auto const EUR = gw["EUR"];
+
1310 env.trust(EUR(100000), alice);
+
1311 env(pay(gw, alice, EUR(10000)));
+
1312 env.trust(EUR(100000), bob);
+
1313 env(pay(gw, bob, EUR(9000)));
+
1314 env.trust(EUR(100000), carol);
+
1315 env(pay(gw, carol, EUR(8000)));
1316 env.close();
-
1317 env.require(flags(gw2, asfAllowTrustLineClawback));
-
1318
-
1319 auto const USD = gw["USD"];
-
1320 env.trust(USD(100000), gw2);
-
1321 env(pay(gw, gw2, USD(5000)));
-
1322 env.trust(USD(100000), alice);
-
1323 env(pay(gw, alice, USD(5000)));
-
1324
-
1325 auto const EUR = gw2["EUR"];
-
1326 env.trust(EUR(100000), gw);
-
1327 env(pay(gw2, gw, EUR(6000)));
-
1328 env.trust(EUR(100000), alice);
-
1329 env(pay(gw2, alice, EUR(6000)));
-
1330 env.close();
-
1331
-
1332 AMM amm(env, gw, USD(1000), EUR(2000), ter(tesSUCCESS));
+
1317
+
1318 AMM amm(env, alice, EUR(2000), USD(8000), ter(tesSUCCESS));
+
1319 env.close();
+
1320
+
1321 BEAST_EXPECT(amm.expectBalances(USD(8000), EUR(2000), IOUAmount(4000)));
+
1322 amm.deposit(bob, USD(4000), EUR(1000));
+
1323 BEAST_EXPECT(
+
1324 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
+
1325 if (!features[fixAMMv1_3])
+
1326 amm.deposit(carol, USD(2000), EUR(500));
+
1327 else
+
1328 amm.deposit(carol, USD(2000.25), EUR(500));
+
1329 BEAST_EXPECT(
+
1330 amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
+
1331 // gw clawback 1000 USD from carol.
+
1332 env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)), ter(tesSUCCESS));
1333 env.close();
-
1334 BEAST_EXPECT(amm.expectBalances(
-
1335 USD(1000), EUR(2000), IOUAmount{1414213562373095, -12}));
+
1334 BEAST_EXPECT(
+
1335 amm.expectBalances(USD(13000), EUR(3250), IOUAmount(6500)));
1336
-
1337 amm.deposit(gw2, USD(2000), EUR(4000));
-
1338 BEAST_EXPECT(amm.expectBalances(
-
1339 USD(3000), EUR(6000), IOUAmount{4242640687119285, -12}));
-
1340
-
1341 amm.deposit(alice, USD(3000), EUR(6000));
-
1342 BEAST_EXPECT(amm.expectBalances(
-
1343 USD(6000), EUR(12000), IOUAmount{8485281374238570, -12}));
-
1344
-
1345 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12}));
-
1346 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{2828427124746190, -12}));
-
1347 BEAST_EXPECT(
-
1348 amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
-
1349
-
1350 // gw claws back 1000 USD from gw2.
-
1351 env(amm::ammClawback(gw, gw2, USD, EUR, USD(1000)), ter(tesSUCCESS));
-
1352 env.close();
-
1353 BEAST_EXPECT(amm.expectBalances(
-
1354 USD(5000), EUR(10000), IOUAmount{7071067811865475, -12}));
-
1355
-
1356 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12}));
-
1357 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
-
1358 BEAST_EXPECT(
-
1359 amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
-
1360
-
1361 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
-
1362 BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
-
1363 BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
-
1364 BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
-
1365
-
1366 // gw2 claws back 1000 EUR from gw.
-
1367 env(amm::ammClawback(gw2, gw, EUR, USD, EUR(1000)), ter(tesSUCCESS));
-
1368 env.close();
-
1369 BEAST_EXPECT(amm.expectBalances(
-
1370 USD(4500),
-
1371 STAmount(EUR, UINT64_C(9000000000000001), -12),
-
1372 IOUAmount{6363961030678928, -12}));
-
1373
-
1374 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
-
1375 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
-
1376 BEAST_EXPECT(
-
1377 amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
+
1337 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
+
1338 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2000)));
+
1339 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
+
1340 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+
1341 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
+
1342 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
+
1343 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
+
1344 if (!features[fixAMMv1_3])
+
1345 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
+
1346 else
+
1347 BEAST_EXPECT(
+
1348 env.balance(carol, USD) ==
+
1349 STAmount(USD, UINT64_C(5999'999999999999), -12));
+
1350 // 250 EUR goes back to carol.
+
1351 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
+
1352
+
1353 // gw clawback 1000 USD from bob with tfClawTwoAssets flag.
+
1354 // then the corresponding EUR will also be clawed back
+
1355 // by gw.
+
1356 env(amm::ammClawback(gw, bob, USD, EUR, USD(1000)),
+
1357 txflags(tfClawTwoAssets),
+
1358 ter(tesSUCCESS));
+
1359 env.close();
+
1360 BEAST_EXPECT(
+
1361 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
+
1362
+
1363 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
+
1364 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
+
1365 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
+
1366 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+
1367 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
+
1368 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
+
1369 // 250 EUR did not go back to bob because tfClawTwoAssets is set.
+
1370 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
+
1371 if (!features[fixAMMv1_3])
+
1372 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
+
1373 else
+
1374 BEAST_EXPECT(
+
1375 env.balance(carol, USD) ==
+
1376 STAmount(USD, UINT64_C(5999'999999999999), -12));
+
1377 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
1378
-
1379 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
-
1380 BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
-
1381 BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
-
1382 BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
-
1383
-
1384 // gw2 claws back 4000 EUR from alice.
-
1385 env(amm::ammClawback(gw2, alice, EUR, USD, EUR(4000)), ter(tesSUCCESS));
-
1386 env.close();
-
1387 BEAST_EXPECT(amm.expectBalances(
-
1388 USD(2500),
-
1389 STAmount(EUR, UINT64_C(5000000000000001), -12),
-
1390 IOUAmount{3535533905932738, -12}));
-
1391
-
1392 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
-
1393 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
-
1394 BEAST_EXPECT(
-
1395 amm.expectLPTokens(alice, IOUAmount{1414213562373095, -12}));
-
1396
-
1397 BEAST_EXPECT(env.balance(alice, USD) == USD(4000));
-
1398 BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
-
1399 BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
-
1400 BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
-
1401 }
-
1402
-
1403 void
-
1404 testNotHoldingLptoken(FeatureBitset features)
-
1405 {
-
1406 testcase(
-
1407 "test AMMClawback from account which does not own any lptoken in "
-
1408 "the pool");
-
1409 using namespace jtx;
-
1410
-
1411 Env env(*this, features);
-
1412 Account gw{"gateway"};
-
1413 Account alice{"alice"};
-
1414 env.fund(XRP(1000000), gw, alice);
-
1415 env.close();
-
1416
-
1417 // gw sets asfAllowTrustLineClawback.
-
1418 env(fset(gw, asfAllowTrustLineClawback));
-
1419 env.close();
-
1420 env.require(flags(gw, asfAllowTrustLineClawback));
-
1421
-
1422 auto const USD = gw["USD"];
-
1423 env.trust(USD(100000), alice);
-
1424 env(pay(gw, alice, USD(5000)));
-
1425
-
1426 AMM amm(env, gw, USD(1000), XRP(2000), ter(tesSUCCESS));
+
1379 // gw clawback all USD from alice and set tfClawTwoAssets.
+
1380 env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
+
1381 txflags(tfClawTwoAssets),
+
1382 ter(tesSUCCESS));
+
1383 env.close();
+
1384 BEAST_EXPECT(amm.expectBalances(USD(4000), EUR(1000), IOUAmount(2000)));
+
1385
+
1386 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+
1387 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
+
1388 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
+
1389 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+
1390 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
+
1391 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
+
1392 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
+
1393 if (!features[fixAMMv1_3])
+
1394 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
+
1395 else
+
1396 BEAST_EXPECT(
+
1397 env.balance(carol, USD) ==
+
1398 STAmount(USD, UINT64_C(5999'999999999999), -12));
+
1399 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
+
1400 }
+
1401
+
1402 void
+
1403 testAMMClawbackSameCurrency(FeatureBitset features)
+
1404 {
+
1405 testcase(
+
1406 "test AMMClawback from AMM pool with assets having the same "
+
1407 "currency, but from different issuer");
+
1408 using namespace jtx;
+
1409
+
1410 // Test AMMClawback for USD/EUR pool. The assets are issued by different
+
1411 // issuer. Claw back all the USD for different users.
+
1412 Env env(*this, features);
+
1413 Account gw{"gateway"};
+
1414 Account gw2{"gateway2"};
+
1415 Account alice{"alice"};
+
1416 Account bob{"bob"};
+
1417 env.fund(XRP(1000000), gw, gw2, alice, bob);
+
1418 env.close();
+
1419
+
1420 // gw sets asfAllowTrustLineClawback.
+
1421 env(fset(gw, asfAllowTrustLineClawback));
+
1422 env.close();
+
1423 env.require(flags(gw, asfAllowTrustLineClawback));
+
1424
+
1425 // gw2 sets asfAllowTrustLineClawback.
+
1426 env(fset(gw2, asfAllowTrustLineClawback));
1427 env.close();
-
1428
-
1429 // Alice did not deposit in the amm pool. So AMMClawback from Alice
-
1430 // will fail.
-
1431 env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
-
1432 ter(tecAMM_BALANCE));
-
1433 }
+
1428 env.require(flags(gw2, asfAllowTrustLineClawback));
+
1429
+
1430 env.trust(gw["USD"](100000), alice);
+
1431 env(pay(gw, alice, gw["USD"](8000)));
+
1432 env.trust(gw["USD"](100000), bob);
+
1433 env(pay(gw, bob, gw["USD"](7000)));
1434
-
1435 void
-
1436 testAssetFrozen(FeatureBitset features)
-
1437 {
-
1438 testcase("test assets frozen");
-
1439 using namespace jtx;
+
1435 env.trust(gw2["USD"](100000), alice);
+
1436 env(pay(gw2, alice, gw2["USD"](6000)));
+
1437 env.trust(gw2["USD"](100000), bob);
+
1438 env(pay(gw2, bob, gw2["USD"](5000)));
+
1439 env.close();
1440
-
1441 // test individually frozen trustline.
-
1442 {
-
1443 Env env(*this, features);
-
1444 Account gw{"gateway"};
-
1445 Account gw2{"gateway2"};
-
1446 Account alice{"alice"};
-
1447 env.fund(XRP(1000000), gw, gw2, alice);
-
1448 env.close();
-
1449
-
1450 // gw sets asfAllowTrustLineClawback.
-
1451 env(fset(gw, asfAllowTrustLineClawback));
-
1452 env.close();
-
1453 env.require(flags(gw, asfAllowTrustLineClawback));
-
1454
-
1455 // gw issues 3000 USD to Alice.
-
1456 auto const USD = gw["USD"];
-
1457 env.trust(USD(100000), alice);
-
1458 env(pay(gw, alice, USD(3000)));
-
1459 env.close();
-
1460 env.require(balance(alice, gw["USD"](3000)));
-
1461
-
1462 // gw2 issues 3000 EUR to Alice.
-
1463 auto const EUR = gw2["EUR"];
-
1464 env.trust(EUR(100000), alice);
-
1465 env(pay(gw2, alice, EUR(3000)));
-
1466 env.close();
-
1467 env.require(balance(alice, gw2["EUR"](3000)));
-
1468
-
1469 // Alice creates AMM pool of EUR/USD.
-
1470 AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
-
1471 env.close();
-
1472
-
1473 BEAST_EXPECT(amm.expectBalances(
-
1474 USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
-
1475
-
1476 // freeze trustline
-
1477 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
1478 env.close();
-
1479
-
1480 // gw clawback 1000 USD from the AMM pool.
-
1481 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
-
1482 ter(tesSUCCESS));
-
1483 env.close();
-
1484
-
1485 env.require(balance(alice, gw["USD"](1000)));
-
1486 env.require(balance(alice, gw2["EUR"](2500)));
-
1487 BEAST_EXPECT(amm.expectBalances(
-
1488 USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
-
1489
-
1490 // Alice has half of its initial lptokens Left.
-
1491 BEAST_EXPECT(
-
1492 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
-
1493
-
1494 // gw clawback another 1000 USD from the AMM pool. The AMM pool will
-
1495 // be empty and get deleted.
-
1496 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
-
1497 ter(tesSUCCESS));
-
1498 env.close();
-
1499
-
1500 // Alice should still has 1000 USD because gw clawed back from the
-
1501 // AMM pool.
-
1502 env.require(balance(alice, gw["USD"](1000)));
-
1503 env.require(balance(alice, gw2["EUR"](3000)));
-
1504
-
1505 // amm is automatically deleted.
-
1506 BEAST_EXPECT(!amm.ammExists());
-
1507 }
-
1508
-
1509 // test individually frozen trustline of both USD and EUR currency.
-
1510 {
-
1511 Env env(*this, features);
-
1512 Account gw{"gateway"};
-
1513 Account gw2{"gateway2"};
-
1514 Account alice{"alice"};
-
1515 env.fund(XRP(1000000), gw, gw2, alice);
-
1516 env.close();
-
1517
-
1518 // gw sets asfAllowTrustLineClawback.
-
1519 env(fset(gw, asfAllowTrustLineClawback));
-
1520 env.close();
-
1521 env.require(flags(gw, asfAllowTrustLineClawback));
+
1441 AMM amm(env, alice, gw["USD"](1000), gw2["USD"](1500), ter(tesSUCCESS));
+
1442 env.close();
+
1443
+
1444 BEAST_EXPECT(amm.expectBalances(
+
1445 gw["USD"](1000),
+
1446 gw2["USD"](1500),
+
1447 IOUAmount{1224744871391589, -12}));
+
1448 amm.deposit(bob, gw["USD"](2000), gw2["USD"](3000));
+
1449 BEAST_EXPECT(amm.expectBalances(
+
1450 gw["USD"](3000),
+
1451 gw2["USD"](4500),
+
1452 IOUAmount{3674234614174767, -12}));
+
1453
+
1454 // Issuer does not match with asset.
+
1455 env(amm::ammClawback(
+
1456 gw,
+
1457 alice,
+
1458 gw2["USD"],
+
1459 gw["USD"],
+
1460 STAmount{Issue{gw2["USD"].currency, gw2.id()}, 500}),
+
1461 ter(temMALFORMED));
+
1462
+
1463 // gw2 clawback 500 gw2[USD] from alice.
+
1464 env(amm::ammClawback(
+
1465 gw2,
+
1466 alice,
+
1467 gw2["USD"],
+
1468 gw["USD"],
+
1469 STAmount{Issue{gw2["USD"].currency, gw2.id()}, 500}),
+
1470 ter(tesSUCCESS));
+
1471 env.close();
+
1472 BEAST_EXPECT(amm.expectBalances(
+
1473 STAmount{gw["USD"], UINT64_C(2666666666666667), -12},
+
1474 gw2["USD"](4000),
+
1475 IOUAmount{3265986323710904, -12}));
+
1476
+
1477 BEAST_EXPECT(
+
1478 amm.expectLPTokens(alice, IOUAmount{8164965809277260, -13}));
+
1479 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2449489742783178, -12}));
+
1480 BEAST_EXPECT(
+
1481 env.balance(alice, gw["USD"]) ==
+
1482 STAmount(gw["USD"], UINT64_C(7333333333333333), -12));
+
1483 BEAST_EXPECT(env.balance(alice, gw2["USD"]) == gw2["USD"](4500));
+
1484 BEAST_EXPECT(env.balance(bob, gw["USD"]) == gw["USD"](5000));
+
1485 BEAST_EXPECT(env.balance(bob, gw2["USD"]) == gw2["USD"](2000));
+
1486
+
1487 // gw clawback all gw["USD"] from bob.
+
1488 env(amm::ammClawback(gw, bob, gw["USD"], gw2["USD"], std::nullopt),
+
1489 ter(tesSUCCESS));
+
1490 env.close();
+
1491 BEAST_EXPECT(amm.expectBalances(
+
1492 STAmount{gw["USD"], UINT64_C(6666666666666670), -13},
+
1493 gw2["USD"](1000),
+
1494 IOUAmount{8164965809277260, -13}));
+
1495
+
1496 BEAST_EXPECT(
+
1497 amm.expectLPTokens(alice, IOUAmount{8164965809277260, -13}));
+
1498 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
+
1499 BEAST_EXPECT(
+
1500 env.balance(alice, gw["USD"]) ==
+
1501 STAmount(gw["USD"], UINT64_C(7333333333333333), -12));
+
1502 BEAST_EXPECT(env.balance(alice, gw2["USD"]) == gw2["USD"](4500));
+
1503 BEAST_EXPECT(env.balance(bob, gw["USD"]) == gw["USD"](5000));
+
1504 // Bob gets 3000 gw2["USD"] back and now his balance is 5000.
+
1505 BEAST_EXPECT(env.balance(bob, gw2["USD"]) == gw2["USD"](5000));
+
1506 }
+
1507
+
1508 void
+
1509 testAMMClawbackIssuesEachOther(FeatureBitset features)
+
1510 {
+
1511 testcase("test AMMClawback when issuing token for each other");
+
1512 using namespace jtx;
+
1513
+
1514 // gw and gw2 issues token for each other. Test AMMClawback from
+
1515 // each other.
+
1516 Env env(*this, features);
+
1517 Account gw{"gateway"};
+
1518 Account gw2{"gateway2"};
+
1519 Account alice{"alice"};
+
1520 env.fund(XRP(1000000), gw, gw2, alice);
+
1521 env.close();
1522
-
1523 // gw issues 3000 USD to Alice.
-
1524 auto const USD = gw["USD"];
-
1525 env.trust(USD(100000), alice);
-
1526 env(pay(gw, alice, USD(3000)));
-
1527 env.close();
-
1528 env.require(balance(alice, gw["USD"](3000)));
-
1529
-
1530 // gw2 issues 3000 EUR to Alice.
-
1531 auto const EUR = gw2["EUR"];
-
1532 env.trust(EUR(100000), alice);
-
1533 env(pay(gw2, alice, EUR(3000)));
-
1534 env.close();
-
1535 env.require(balance(alice, gw2["EUR"](3000)));
-
1536
-
1537 // Alice creates AMM pool of EUR/USD.
-
1538 AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
-
1539 env.close();
-
1540
-
1541 BEAST_EXPECT(amm.expectBalances(
-
1542 USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
-
1543
-
1544 // freeze trustlines
-
1545 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
1546 env(trust(gw2, alice["EUR"](0), tfSetFreeze));
-
1547 env.close();
-
1548
-
1549 // gw clawback 1000 USD from the AMM pool.
-
1550 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
-
1551 ter(tesSUCCESS));
-
1552 env.close();
-
1553
-
1554 env.require(balance(alice, gw["USD"](1000)));
-
1555 env.require(balance(alice, gw2["EUR"](2500)));
-
1556 BEAST_EXPECT(amm.expectBalances(
-
1557 USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
-
1558 BEAST_EXPECT(
-
1559 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
-
1560 }
-
1561
-
1562 // test gw global freeze.
-
1563 {
-
1564 Env env(*this, features);
-
1565 Account gw{"gateway"};
-
1566 Account gw2{"gateway2"};
-
1567 Account alice{"alice"};
-
1568 env.fund(XRP(1000000), gw, gw2, alice);
-
1569 env.close();
-
1570
-
1571 // gw sets asfAllowTrustLineClawback.
-
1572 env(fset(gw, asfAllowTrustLineClawback));
-
1573 env.close();
-
1574 env.require(flags(gw, asfAllowTrustLineClawback));
-
1575
-
1576 // gw issues 3000 USD to Alice.
-
1577 auto const USD = gw["USD"];
-
1578 env.trust(USD(100000), alice);
-
1579 env(pay(gw, alice, USD(3000)));
-
1580 env.close();
-
1581 env.require(balance(alice, gw["USD"](3000)));
-
1582
-
1583 // gw2 issues 3000 EUR to Alice.
-
1584 auto const EUR = gw2["EUR"];
-
1585 env.trust(EUR(100000), alice);
-
1586 env(pay(gw2, alice, EUR(3000)));
-
1587 env.close();
-
1588 env.require(balance(alice, gw2["EUR"](3000)));
-
1589
-
1590 // Alice creates AMM pool of EUR/USD.
-
1591 AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
-
1592 env.close();
-
1593
-
1594 BEAST_EXPECT(amm.expectBalances(
-
1595 USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
-
1596
-
1597 // global freeze
-
1598 env(fset(gw, asfGlobalFreeze));
-
1599 env.close();
-
1600
-
1601 // gw clawback 1000 USD from the AMM pool.
-
1602 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
-
1603 ter(tesSUCCESS));
-
1604 env.close();
-
1605
-
1606 env.require(balance(alice, gw["USD"](1000)));
-
1607 env.require(balance(alice, gw2["EUR"](2500)));
-
1608 BEAST_EXPECT(amm.expectBalances(
-
1609 USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
-
1610 BEAST_EXPECT(
-
1611 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
-
1612 }
-
1613
-
1614 // Test both assets are issued by the same issuer. And issuer sets
-
1615 // global freeze.
-
1616 {
-
1617 Env env(*this, features);
-
1618 Account gw{"gateway"};
-
1619 Account alice{"alice"};
-
1620 Account bob{"bob"};
-
1621 Account carol{"carol"};
-
1622 env.fund(XRP(1000000), gw, alice, bob, carol);
-
1623 env.close();
-
1624
-
1625 // gw sets asfAllowTrustLineClawback.
-
1626 env(fset(gw, asfAllowTrustLineClawback));
-
1627 env.close();
-
1628 env.require(flags(gw, asfAllowTrustLineClawback));
-
1629
-
1630 auto const USD = gw["USD"];
-
1631 env.trust(USD(100000), alice);
-
1632 env(pay(gw, alice, USD(10000)));
-
1633 env.trust(USD(100000), bob);
-
1634 env(pay(gw, bob, USD(9000)));
-
1635 env.trust(USD(100000), carol);
-
1636 env(pay(gw, carol, USD(8000)));
-
1637 env.close();
-
1638
-
1639 auto const EUR = gw["EUR"];
-
1640 env.trust(EUR(100000), alice);
-
1641 env(pay(gw, alice, EUR(10000)));
-
1642 env.trust(EUR(100000), bob);
-
1643 env(pay(gw, bob, EUR(9000)));
-
1644 env.trust(EUR(100000), carol);
-
1645 env(pay(gw, carol, EUR(8000)));
-
1646 env.close();
-
1647
-
1648 AMM amm(env, alice, EUR(2000), USD(8000), ter(tesSUCCESS));
-
1649 env.close();
-
1650
-
1651 BEAST_EXPECT(
-
1652 amm.expectBalances(USD(8000), EUR(2000), IOUAmount(4000)));
-
1653 amm.deposit(bob, USD(4000), EUR(1000));
-
1654 BEAST_EXPECT(
-
1655 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
-
1656 amm.deposit(carol, USD(2000), EUR(500));
-
1657 BEAST_EXPECT(
-
1658 amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
-
1659
-
1660 // global freeze
-
1661 env(fset(gw, asfGlobalFreeze));
-
1662 env.close();
-
1663
-
1664 // gw clawback 1000 USD from carol.
-
1665 env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)),
-
1666 ter(tesSUCCESS));
-
1667 env.close();
-
1668 BEAST_EXPECT(
-
1669 amm.expectBalances(USD(13000), EUR(3250), IOUAmount(6500)));
-
1670
-
1671 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
-
1672 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2000)));
-
1673 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
-
1674 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
-
1675 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
-
1676 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
-
1677 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
-
1678 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
-
1679 // 250 EUR goes back to carol.
-
1680 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
+
1523 // gw sets asfAllowTrustLineClawback.
+
1524 env(fset(gw, asfAllowTrustLineClawback));
+
1525 env.close();
+
1526 env.require(flags(gw, asfAllowTrustLineClawback));
+
1527
+
1528 // gw2 sets asfAllowTrustLineClawback.
+
1529 env(fset(gw2, asfAllowTrustLineClawback));
+
1530 env.close();
+
1531 env.require(flags(gw2, asfAllowTrustLineClawback));
+
1532
+
1533 auto const USD = gw["USD"];
+
1534 env.trust(USD(100000), gw2);
+
1535 env(pay(gw, gw2, USD(5000)));
+
1536 env.trust(USD(100000), alice);
+
1537 env(pay(gw, alice, USD(5000)));
+
1538
+
1539 auto const EUR = gw2["EUR"];
+
1540 env.trust(EUR(100000), gw);
+
1541 env(pay(gw2, gw, EUR(6000)));
+
1542 env.trust(EUR(100000), alice);
+
1543 env(pay(gw2, alice, EUR(6000)));
+
1544 env.close();
+
1545
+
1546 AMM amm(env, gw, USD(1000), EUR(2000), ter(tesSUCCESS));
+
1547 env.close();
+
1548 BEAST_EXPECT(amm.expectBalances(
+
1549 USD(1000), EUR(2000), IOUAmount{1414213562373095, -12}));
+
1550
+
1551 amm.deposit(gw2, USD(2000), EUR(4000));
+
1552 BEAST_EXPECT(amm.expectBalances(
+
1553 USD(3000), EUR(6000), IOUAmount{4242640687119285, -12}));
+
1554
+
1555 amm.deposit(alice, USD(3000), EUR(6000));
+
1556 BEAST_EXPECT(amm.expectBalances(
+
1557 USD(6000), EUR(12000), IOUAmount{8485281374238570, -12}));
+
1558
+
1559 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12}));
+
1560 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{2828427124746190, -12}));
+
1561 BEAST_EXPECT(
+
1562 amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
+
1563
+
1564 // gw claws back 1000 USD from gw2.
+
1565 env(amm::ammClawback(gw, gw2, USD, EUR, USD(1000)), ter(tesSUCCESS));
+
1566 env.close();
+
1567 BEAST_EXPECT(amm.expectBalances(
+
1568 USD(5000), EUR(10000), IOUAmount{7071067811865475, -12}));
+
1569
+
1570 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12}));
+
1571 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
+
1572 BEAST_EXPECT(
+
1573 amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
+
1574
+
1575 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+
1576 BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
+
1577 BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
+
1578 BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
+
1579
+
1580 // gw2 claws back 1000 EUR from gw.
+
1581 env(amm::ammClawback(gw2, gw, EUR, USD, EUR(1000)), ter(tesSUCCESS));
+
1582 env.close();
+
1583 if (!features[fixAMMv1_3])
+
1584 BEAST_EXPECT(amm.expectBalances(
+
1585 USD(4500),
+
1586 STAmount(EUR, UINT64_C(9000000000000001), -12),
+
1587 IOUAmount{6363961030678928, -12}));
+
1588 else
+
1589 BEAST_EXPECT(amm.expectBalances(
+
1590 USD(4500), EUR(9000), IOUAmount{6363961030678928, -12}));
+
1591
+
1592 if (!features[fixAMMv1_3])
+
1593 BEAST_EXPECT(
+
1594 amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
+
1595 else
+
1596 BEAST_EXPECT(
+
1597 amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13}));
+
1598 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
+
1599 BEAST_EXPECT(
+
1600 amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
+
1601
+
1602 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+
1603 BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
+
1604 BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
+
1605 BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
+
1606
+
1607 // gw2 claws back 4000 EUR from alice.
+
1608 env(amm::ammClawback(gw2, alice, EUR, USD, EUR(4000)), ter(tesSUCCESS));
+
1609 env.close();
+
1610 if (!features[fixAMMv1_3])
+
1611 BEAST_EXPECT(amm.expectBalances(
+
1612 USD(2500),
+
1613 STAmount(EUR, UINT64_C(5000000000000001), -12),
+
1614 IOUAmount{3535533905932738, -12}));
+
1615 else
+
1616 BEAST_EXPECT(amm.expectBalances(
+
1617 USD(2500), EUR(5000), IOUAmount{3535533905932738, -12}));
+
1618
+
1619 if (!features[fixAMMv1_3])
+
1620 BEAST_EXPECT(
+
1621 amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
+
1622 else
+
1623 BEAST_EXPECT(
+
1624 amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13}));
+
1625 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
+
1626 BEAST_EXPECT(
+
1627 amm.expectLPTokens(alice, IOUAmount{1414213562373095, -12}));
+
1628
+
1629 BEAST_EXPECT(env.balance(alice, USD) == USD(4000));
+
1630 BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
+
1631 BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
+
1632 BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
+
1633 }
+
1634
+
1635 void
+
1636 testNotHoldingLptoken(FeatureBitset features)
+
1637 {
+
1638 testcase(
+
1639 "test AMMClawback from account which does not own any lptoken in "
+
1640 "the pool");
+
1641 using namespace jtx;
+
1642
+
1643 Env env(*this, features);
+
1644 Account gw{"gateway"};
+
1645 Account alice{"alice"};
+
1646 env.fund(XRP(1000000), gw, alice);
+
1647 env.close();
+
1648
+
1649 // gw sets asfAllowTrustLineClawback.
+
1650 env(fset(gw, asfAllowTrustLineClawback));
+
1651 env.close();
+
1652 env.require(flags(gw, asfAllowTrustLineClawback));
+
1653
+
1654 auto const USD = gw["USD"];
+
1655 env.trust(USD(100000), alice);
+
1656 env(pay(gw, alice, USD(5000)));
+
1657
+
1658 AMM amm(env, gw, USD(1000), XRP(2000), ter(tesSUCCESS));
+
1659 env.close();
+
1660
+
1661 // Alice did not deposit in the amm pool. So AMMClawback from Alice
+
1662 // will fail.
+
1663 env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
+
1664 ter(tecAMM_BALANCE));
+
1665 }
+
1666
+
1667 void
+
1668 testAssetFrozen(FeatureBitset features)
+
1669 {
+
1670 testcase("test assets frozen");
+
1671 using namespace jtx;
+
1672
+
1673 // test individually frozen trustline.
+
1674 {
+
1675 Env env(*this, features);
+
1676 Account gw{"gateway"};
+
1677 Account gw2{"gateway2"};
+
1678 Account alice{"alice"};
+
1679 env.fund(XRP(1000000), gw, gw2, alice);
+
1680 env.close();
1681
-
1682 // gw clawback 1000 USD from bob with tfClawTwoAssets flag.
-
1683 // then the corresponding EUR will also be clawed back
-
1684 // by gw.
-
1685 env(amm::ammClawback(gw, bob, USD, EUR, USD(1000)),
-
1686 txflags(tfClawTwoAssets),
-
1687 ter(tesSUCCESS));
-
1688 env.close();
-
1689 BEAST_EXPECT(
-
1690 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
-
1691
-
1692 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
-
1693 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
-
1694 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
-
1695 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
-
1696 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
-
1697 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
-
1698 // 250 EUR did not go back to bob because tfClawTwoAssets is set.
-
1699 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
-
1700 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
-
1701 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
-
1702
-
1703 // gw clawback all USD from alice and set tfClawTwoAssets.
-
1704 env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
-
1705 txflags(tfClawTwoAssets),
-
1706 ter(tesSUCCESS));
-
1707 env.close();
-
1708 BEAST_EXPECT(
-
1709 amm.expectBalances(USD(4000), EUR(1000), IOUAmount(2000)));
-
1710
-
1711 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
-
1712 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
-
1713 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
-
1714 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
-
1715 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
-
1716 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
-
1717 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
-
1718 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
-
1719 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
-
1720 }
-
1721 }
-
1722
-
1723 void
-
1724 testSingleDepositAndClawback(FeatureBitset features)
-
1725 {
-
1726 testcase("test single depoit and clawback");
-
1727 using namespace jtx;
-
1728
-
1729 // Test AMMClawback for USD/XRP pool. Claw back USD, and XRP goes back
-
1730 // to the holder.
-
1731 Env env(*this, features);
-
1732 Account gw{"gateway"};
-
1733 Account alice{"alice"};
-
1734 env.fund(XRP(1000000000), gw, alice);
-
1735 env.close();
+
1682 // gw sets asfAllowTrustLineClawback.
+
1683 env(fset(gw, asfAllowTrustLineClawback));
+
1684 env.close();
+
1685 env.require(flags(gw, asfAllowTrustLineClawback));
+
1686
+
1687 // gw issues 3000 USD to Alice.
+
1688 auto const USD = gw["USD"];
+
1689 env.trust(USD(100000), alice);
+
1690 env(pay(gw, alice, USD(3000)));
+
1691 env.close();
+
1692 env.require(balance(alice, gw["USD"](3000)));
+
1693
+
1694 // gw2 issues 3000 EUR to Alice.
+
1695 auto const EUR = gw2["EUR"];
+
1696 env.trust(EUR(100000), alice);
+
1697 env(pay(gw2, alice, EUR(3000)));
+
1698 env.close();
+
1699 env.require(balance(alice, gw2["EUR"](3000)));
+
1700
+
1701 // Alice creates AMM pool of EUR/USD.
+
1702 AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
+
1703 env.close();
+
1704
+
1705 BEAST_EXPECT(amm.expectBalances(
+
1706 USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
+
1707
+
1708 // freeze trustline
+
1709 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
1710 env.close();
+
1711
+
1712 // gw clawback 1000 USD from the AMM pool.
+
1713 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
+
1714 ter(tesSUCCESS));
+
1715 env.close();
+
1716
+
1717 env.require(balance(alice, gw["USD"](1000)));
+
1718 env.require(balance(alice, gw2["EUR"](2500)));
+
1719 BEAST_EXPECT(amm.expectBalances(
+
1720 USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
+
1721
+
1722 // Alice has half of its initial lptokens Left.
+
1723 BEAST_EXPECT(
+
1724 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
+
1725
+
1726 // gw clawback another 1000 USD from the AMM pool. The AMM pool will
+
1727 // be empty and get deleted.
+
1728 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
+
1729 ter(tesSUCCESS));
+
1730 env.close();
+
1731
+
1732 // Alice should still has 1000 USD because gw clawed back from the
+
1733 // AMM pool.
+
1734 env.require(balance(alice, gw["USD"](1000)));
+
1735 env.require(balance(alice, gw2["EUR"](3000)));
1736
-
1737 // gw sets asfAllowTrustLineClawback.
-
1738 env(fset(gw, asfAllowTrustLineClawback));
-
1739 env.close();
-
1740 env.require(flags(gw, asfAllowTrustLineClawback));
-
1741
-
1742 // gw issues 1000 USD to Alice.
-
1743 auto const USD = gw["USD"];
-
1744 env.trust(USD(100000), alice);
-
1745 env(pay(gw, alice, USD(1000)));
-
1746 env.close();
-
1747 env.require(balance(alice, gw["USD"](1000)));
-
1748
-
1749 // gw creates AMM pool of XRP/USD.
-
1750 AMM amm(env, gw, XRP(100), USD(400), ter(tesSUCCESS));
-
1751 env.close();
-
1752
-
1753 BEAST_EXPECT(amm.expectBalances(USD(400), XRP(100), IOUAmount(200000)));
+
1737 // amm is automatically deleted.
+
1738 BEAST_EXPECT(!amm.ammExists());
+
1739 }
+
1740
+
1741 // test individually frozen trustline of both USD and EUR currency.
+
1742 {
+
1743 Env env(*this, features);
+
1744 Account gw{"gateway"};
+
1745 Account gw2{"gateway2"};
+
1746 Account alice{"alice"};
+
1747 env.fund(XRP(1000000), gw, gw2, alice);
+
1748 env.close();
+
1749
+
1750 // gw sets asfAllowTrustLineClawback.
+
1751 env(fset(gw, asfAllowTrustLineClawback));
+
1752 env.close();
+
1753 env.require(flags(gw, asfAllowTrustLineClawback));
1754
-
1755 amm.deposit(alice, USD(400));
-
1756 env.close();
-
1757
-
1758 BEAST_EXPECT(amm.expectBalances(
-
1759 USD(800), XRP(100), IOUAmount{2828427124746190, -10}));
-
1760
-
1761 auto aliceXrpBalance = env.balance(alice, XRP);
-
1762
-
1763 env(amm::ammClawback(gw, alice, USD, XRP, USD(400)), ter(tesSUCCESS));
-
1764 env.close();
-
1765
-
1766 BEAST_EXPECT(amm.expectBalances(
-
1767 STAmount(USD, UINT64_C(5656854249492380), -13),
-
1768 XRP(70.710678),
-
1769 IOUAmount(200000)));
-
1770 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
-
1771 BEAST_EXPECT(expectLedgerEntryRoot(
-
1772 env, alice, aliceXrpBalance + XRP(29.289322)));
-
1773 }
-
1774
-
1775 void
-
1776 run() override
-
1777 {
-
1778 FeatureBitset const all{jtx::supported_amendments()};
-
1779 testInvalidRequest(all);
-
1780 testFeatureDisabled(all - featureAMMClawback);
-
1781 testAMMClawbackSpecificAmount(all);
-
1782 testAMMClawbackExceedBalance(all);
-
1783 testAMMClawbackAll(all);
-
1784 testAMMClawbackSameIssuerAssets(all);
-
1785 testAMMClawbackSameCurrency(all);
-
1786 testAMMClawbackIssuesEachOther(all);
-
1787 testNotHoldingLptoken(all);
-
1788 testAssetFrozen(all);
-
1789 testSingleDepositAndClawback(all);
-
1790 }
-
1791};
-
1792BEAST_DEFINE_TESTSUITE(AMMClawback, app, ripple);
-
1793} // namespace test
-
1794} // namespace ripple
+
1755 // gw issues 3000 USD to Alice.
+
1756 auto const USD = gw["USD"];
+
1757 env.trust(USD(100000), alice);
+
1758 env(pay(gw, alice, USD(3000)));
+
1759 env.close();
+
1760 env.require(balance(alice, gw["USD"](3000)));
+
1761
+
1762 // gw2 issues 3000 EUR to Alice.
+
1763 auto const EUR = gw2["EUR"];
+
1764 env.trust(EUR(100000), alice);
+
1765 env(pay(gw2, alice, EUR(3000)));
+
1766 env.close();
+
1767 env.require(balance(alice, gw2["EUR"](3000)));
+
1768
+
1769 // Alice creates AMM pool of EUR/USD.
+
1770 AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
+
1771 env.close();
+
1772
+
1773 BEAST_EXPECT(amm.expectBalances(
+
1774 USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
+
1775
+
1776 // freeze trustlines
+
1777 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
1778 env(trust(gw2, alice["EUR"](0), tfSetFreeze));
+
1779 env.close();
+
1780
+
1781 // gw clawback 1000 USD from the AMM pool.
+
1782 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
+
1783 ter(tesSUCCESS));
+
1784 env.close();
+
1785
+
1786 env.require(balance(alice, gw["USD"](1000)));
+
1787 env.require(balance(alice, gw2["EUR"](2500)));
+
1788 BEAST_EXPECT(amm.expectBalances(
+
1789 USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
+
1790 BEAST_EXPECT(
+
1791 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
+
1792 }
+
1793
+
1794 // test gw global freeze.
+
1795 {
+
1796 Env env(*this, features);
+
1797 Account gw{"gateway"};
+
1798 Account gw2{"gateway2"};
+
1799 Account alice{"alice"};
+
1800 env.fund(XRP(1000000), gw, gw2, alice);
+
1801 env.close();
+
1802
+
1803 // gw sets asfAllowTrustLineClawback.
+
1804 env(fset(gw, asfAllowTrustLineClawback));
+
1805 env.close();
+
1806 env.require(flags(gw, asfAllowTrustLineClawback));
+
1807
+
1808 // gw issues 3000 USD to Alice.
+
1809 auto const USD = gw["USD"];
+
1810 env.trust(USD(100000), alice);
+
1811 env(pay(gw, alice, USD(3000)));
+
1812 env.close();
+
1813 env.require(balance(alice, gw["USD"](3000)));
+
1814
+
1815 // gw2 issues 3000 EUR to Alice.
+
1816 auto const EUR = gw2["EUR"];
+
1817 env.trust(EUR(100000), alice);
+
1818 env(pay(gw2, alice, EUR(3000)));
+
1819 env.close();
+
1820 env.require(balance(alice, gw2["EUR"](3000)));
+
1821
+
1822 // Alice creates AMM pool of EUR/USD.
+
1823 AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
+
1824 env.close();
+
1825
+
1826 BEAST_EXPECT(amm.expectBalances(
+
1827 USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
+
1828
+
1829 // global freeze
+
1830 env(fset(gw, asfGlobalFreeze));
+
1831 env.close();
+
1832
+
1833 // gw clawback 1000 USD from the AMM pool.
+
1834 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
+
1835 ter(tesSUCCESS));
+
1836 env.close();
+
1837
+
1838 env.require(balance(alice, gw["USD"](1000)));
+
1839 env.require(balance(alice, gw2["EUR"](2500)));
+
1840 BEAST_EXPECT(amm.expectBalances(
+
1841 USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
+
1842 BEAST_EXPECT(
+
1843 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
+
1844 }
+
1845
+
1846 // Test both assets are issued by the same issuer. And issuer sets
+
1847 // global freeze.
+
1848 {
+
1849 Env env(*this, features);
+
1850 Account gw{"gateway"};
+
1851 Account alice{"alice"};
+
1852 Account bob{"bob"};
+
1853 Account carol{"carol"};
+
1854 env.fund(XRP(1000000), gw, alice, bob, carol);
+
1855 env.close();
+
1856
+
1857 // gw sets asfAllowTrustLineClawback.
+
1858 env(fset(gw, asfAllowTrustLineClawback));
+
1859 env.close();
+
1860 env.require(flags(gw, asfAllowTrustLineClawback));
+
1861
+
1862 auto const USD = gw["USD"];
+
1863 env.trust(USD(100000), alice);
+
1864 env(pay(gw, alice, USD(10000)));
+
1865 env.trust(USD(100000), bob);
+
1866 env(pay(gw, bob, USD(9000)));
+
1867 env.trust(USD(100000), carol);
+
1868 env(pay(gw, carol, USD(8000)));
+
1869 env.close();
+
1870
+
1871 auto const EUR = gw["EUR"];
+
1872 env.trust(EUR(100000), alice);
+
1873 env(pay(gw, alice, EUR(10000)));
+
1874 env.trust(EUR(100000), bob);
+
1875 env(pay(gw, bob, EUR(9000)));
+
1876 env.trust(EUR(100000), carol);
+
1877 env(pay(gw, carol, EUR(8000)));
+
1878 env.close();
+
1879
+
1880 AMM amm(env, alice, EUR(2000), USD(8000), ter(tesSUCCESS));
+
1881 env.close();
+
1882
+
1883 BEAST_EXPECT(
+
1884 amm.expectBalances(USD(8000), EUR(2000), IOUAmount(4000)));
+
1885 amm.deposit(bob, USD(4000), EUR(1000));
+
1886 BEAST_EXPECT(
+
1887 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
+
1888 if (!features[fixAMMv1_3])
+
1889 amm.deposit(carol, USD(2000), EUR(500));
+
1890 else
+
1891 amm.deposit(carol, USD(2000.25), EUR(500));
+
1892 BEAST_EXPECT(
+
1893 amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
+
1894
+
1895 // global freeze
+
1896 env(fset(gw, asfGlobalFreeze));
+
1897 env.close();
+
1898
+
1899 // gw clawback 1000 USD from carol.
+
1900 env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)),
+
1901 ter(tesSUCCESS));
+
1902 env.close();
+
1903 BEAST_EXPECT(
+
1904 amm.expectBalances(USD(13000), EUR(3250), IOUAmount(6500)));
+
1905
+
1906 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
+
1907 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2000)));
+
1908 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
+
1909 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+
1910 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
+
1911 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
+
1912 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
+
1913 if (!features[fixAMMv1_3])
+
1914 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
+
1915 else
+
1916 BEAST_EXPECT(
+
1917 env.balance(carol, USD) ==
+
1918 STAmount(USD, UINT64_C(5999'999999999999), -12));
+
1919 // 250 EUR goes back to carol.
+
1920 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
+
1921
+
1922 // gw clawback 1000 USD from bob with tfClawTwoAssets flag.
+
1923 // then the corresponding EUR will also be clawed back
+
1924 // by gw.
+
1925 env(amm::ammClawback(gw, bob, USD, EUR, USD(1000)),
+
1926 txflags(tfClawTwoAssets),
+
1927 ter(tesSUCCESS));
+
1928 env.close();
+
1929 BEAST_EXPECT(
+
1930 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
+
1931
+
1932 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
+
1933 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
+
1934 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
+
1935 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+
1936 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
+
1937 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
+
1938 // 250 EUR did not go back to bob because tfClawTwoAssets is set.
+
1939 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
+
1940 if (!features[fixAMMv1_3])
+
1941 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
+
1942 else
+
1943 BEAST_EXPECT(
+
1944 env.balance(carol, USD) ==
+
1945 STAmount(USD, UINT64_C(5999'999999999999), -12));
+
1946 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
+
1947
+
1948 // gw clawback all USD from alice and set tfClawTwoAssets.
+
1949 env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
+
1950 txflags(tfClawTwoAssets),
+
1951 ter(tesSUCCESS));
+
1952 env.close();
+
1953 BEAST_EXPECT(
+
1954 amm.expectBalances(USD(4000), EUR(1000), IOUAmount(2000)));
+
1955
+
1956 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+
1957 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
+
1958 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
+
1959 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+
1960 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
+
1961 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
+
1962 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
+
1963 if (!features[fixAMMv1_3])
+
1964 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
+
1965 else
+
1966 BEAST_EXPECT(
+
1967 env.balance(carol, USD) ==
+
1968 STAmount(USD, UINT64_C(5999'999999999999), -12));
+
1969 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
+
1970 }
+
1971 }
+
1972
+
1973 void
+
1974 testSingleDepositAndClawback(FeatureBitset features)
+
1975 {
+
1976 testcase("test single depoit and clawback");
+
1977 using namespace jtx;
+
1978
+
1979 // Test AMMClawback for USD/XRP pool. Claw back USD, and XRP goes back
+
1980 // to the holder.
+
1981 Env env(*this, features);
+
1982 Account gw{"gateway"};
+
1983 Account alice{"alice"};
+
1984 env.fund(XRP(1000000000), gw, alice);
+
1985 env.close();
+
1986
+
1987 // gw sets asfAllowTrustLineClawback.
+
1988 env(fset(gw, asfAllowTrustLineClawback));
+
1989 env.close();
+
1990 env.require(flags(gw, asfAllowTrustLineClawback));
+
1991
+
1992 // gw issues 1000 USD to Alice.
+
1993 auto const USD = gw["USD"];
+
1994 env.trust(USD(100000), alice);
+
1995 env(pay(gw, alice, USD(1000)));
+
1996 env.close();
+
1997 env.require(balance(alice, gw["USD"](1000)));
+
1998
+
1999 // gw creates AMM pool of XRP/USD.
+
2000 AMM amm(env, gw, XRP(100), USD(400), ter(tesSUCCESS));
+
2001 env.close();
+
2002
+
2003 BEAST_EXPECT(amm.expectBalances(USD(400), XRP(100), IOUAmount(200000)));
+
2004
+
2005 amm.deposit(alice, USD(400));
+
2006 env.close();
+
2007
+
2008 BEAST_EXPECT(amm.expectBalances(
+
2009 USD(800), XRP(100), IOUAmount{2828427124746190, -10}));
+
2010
+
2011 auto aliceXrpBalance = env.balance(alice, XRP);
+
2012
+
2013 env(amm::ammClawback(gw, alice, USD, XRP, USD(400)), ter(tesSUCCESS));
+
2014 env.close();
+
2015
+
2016 if (!features[fixAMMv1_3])
+
2017 BEAST_EXPECT(amm.expectBalances(
+
2018 STAmount(USD, UINT64_C(5656854249492380), -13),
+
2019 XRP(70.710678),
+
2020 IOUAmount(200000)));
+
2021 else
+
2022 BEAST_EXPECT(amm.expectBalances(
+
2023 STAmount(USD, UINT64_C(565'685424949238), -12),
+
2024 XRP(70.710679),
+
2025 IOUAmount(200000)));
+
2026 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+
2027 if (!features[fixAMMv1_3])
+
2028 BEAST_EXPECT(expectLedgerEntryRoot(
+
2029 env, alice, aliceXrpBalance + XRP(29.289322)));
+
2030 else
+
2031 BEAST_EXPECT(expectLedgerEntryRoot(
+
2032 env, alice, aliceXrpBalance + XRP(29.289321)));
+
2033 }
+
2034
+
2035 void
+
2036 run() override
+
2037 {
+
2038 FeatureBitset const all{jtx::supported_amendments()};
+
2039 testInvalidRequest(all);
+
2040 testFeatureDisabled(all - featureAMMClawback);
+
2041 testAMMClawbackSpecificAmount(all);
+
2042 testAMMClawbackExceedBalance(all);
+
2043 testAMMClawbackExceedBalance(all - fixAMMv1_3);
+
2044 testAMMClawbackAll(all);
+
2045 testAMMClawbackAll(all - fixAMMv1_3);
+
2046 testAMMClawbackSameIssuerAssets(all);
+
2047 testAMMClawbackSameIssuerAssets(all - fixAMMv1_3);
+
2048 testAMMClawbackSameCurrency(all);
+
2049 testAMMClawbackIssuesEachOther(all);
+
2050 testNotHoldingLptoken(all);
+
2051 testAssetFrozen(all);
+
2052 testAssetFrozen(all - fixAMMv1_3);
+
2053 testSingleDepositAndClawback(all);
+
2054 testSingleDepositAndClawback(all - fixAMMv1_3);
+
2055 }
+
2056};
+
2057BEAST_DEFINE_TESTSUITE(AMMClawback, app, ripple);
+
2058} // namespace test
+
2059} // namespace ripple
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
Definition: AMMClawback.h:28
Definition: Feature.h:147
@@ -1877,30 +2142,31 @@ $(function() {
A currency issued by an account.
Definition: Issue.h:36
Currency currency
Definition: Issue.h:38
Definition: STAmount.h:50
+
Definition: XRPAmount.h:43
void testInvalidRequest(FeatureBitset features)
-
void testAMMClawbackSameCurrency(FeatureBitset features)
-
void testNotHoldingLptoken(FeatureBitset features)
-
void testSingleDepositAndClawback(FeatureBitset features)
-
void testAMMClawbackAll(FeatureBitset features)
+
void testAMMClawbackSameCurrency(FeatureBitset features)
+
void testNotHoldingLptoken(FeatureBitset features)
+
void testSingleDepositAndClawback(FeatureBitset features)
+
void testAMMClawbackAll(FeatureBitset features)
void testAMMClawbackSpecificAmount(FeatureBitset features)
void testFeatureDisabled(FeatureBitset features)
void testAMMClawbackExceedBalance(FeatureBitset features)
-
void run() override
Runs the suite.
-
void testAssetFrozen(FeatureBitset features)
-
void testAMMClawbackSameIssuerAssets(FeatureBitset features)
-
void testAMMClawbackIssuesEachOther(FeatureBitset features)
-
jtx::Account const alice
Definition: AMMTest.h:68
-
jtx::IOU const USD
Definition: AMMTest.h:70
-
jtx::Account const gw
Definition: AMMTest.h:66
-
jtx::Account const bob
Definition: AMMTest.h:69
-
jtx::IOU const EUR
Definition: AMMTest.h:71
-
jtx::Account const carol
Definition: AMMTest.h:67
-
Definition: AMMTest.h:93
+
void run() override
Runs the suite.
+
void testAssetFrozen(FeatureBitset features)
+
void testAMMClawbackSameIssuerAssets(FeatureBitset features)
+
void testAMMClawbackIssuesEachOther(FeatureBitset features)
+
jtx::Account const alice
Definition: AMMTest.h:77
+
jtx::IOU const USD
Definition: AMMTest.h:79
+
jtx::Account const gw
Definition: AMMTest.h:75
+
jtx::Account const bob
Definition: AMMTest.h:78
+
jtx::IOU const EUR
Definition: AMMTest.h:80
+
jtx::Account const carol
Definition: AMMTest.h:76
+
Definition: AMMTest.h:107
Convenience class to test AMM functionality.
Definition: AMM.h:124
-
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition: AMM.cpp:232
-
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:411
-
bool expectLPTokens(AccountID const &account, IOUAmount const &tokens) const
Definition: AMM.cpp:262
+
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition: AMM.cpp:237
+
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:416
+
bool expectLPTokens(AccountID const &account, IOUAmount const &tokens) const
Definition: AMM.cpp:267
Immutable cryptographic account descriptor.
Definition: Account.h:39
AccountID id() const
Returns the Account ID.
Definition: Account.h:107
A transaction testing environment.
Definition: Env.h:121
@@ -1914,7 +2180,7 @@ $(function() {
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:35
Set the flags on a JTx.
Definition: txflags.h:31
-
Json::Value ammClawback(Account const &issuer, Account const &holder, Issue const &asset, Issue const &asset2, std::optional< STAmount > const &amount)
Definition: AMM.cpp:828
+
Json::Value ammClawback(Account const &issuer, Account const &holder, Issue const &asset, Issue const &asset2, std::optional< STAmount > const &amount)
Definition: AMM.cpp:833
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition: trust.cpp:32
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition: flags.cpp:29
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
diff --git a/AMMCore_8cpp_source.html b/AMMCore_8cpp_source.html index 46cf66b692..77f9be17de 100644 --- a/AMMCore_8cpp_source.html +++ b/AMMCore_8cpp_source.html @@ -214,7 +214,7 @@ $(function() {
A currency issued by an account.
Definition: Issue.h:36
AccountID account
Definition: Issue.h:39
Currency currency
Definition: Issue.h:38
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
Definition: STAmount.h:50
Issue const & issue() const
Definition: STAmount.h:496
diff --git a/AMMCore_8h_source.html b/AMMCore_8h_source.html index 99b925be4a..0aee9709ee 100644 --- a/AMMCore_8h_source.html +++ b/AMMCore_8h_source.html @@ -181,7 +181,7 @@ $(function() {
126#endif // RIPPLE_PROTOCOL_AMMCORE_H_INCLUDED
A currency issued by an account.
Definition: Issue.h:36
Definition: Number.h:36
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
Definition: STAmount.h:50
Definition: STObject.h:57
Definition: TER.h:409
diff --git a/AMMDeposit_8cpp_source.html b/AMMDeposit_8cpp_source.html index b373478068..122ca4459e 100644 --- a/AMMDeposit_8cpp_source.html +++ b/AMMDeposit_8cpp_source.html @@ -613,14 +613,14 @@ $(function() {
535
536 auto const
537 [amountDepositActual, amount2DepositActual, lpTokensDepositActual] =
-
538 adjustAmountsByLPTokens(
+
538 adjustAmountsByLPTokens(
539 amountBalance,
540 amountDeposit,
541 amount2Deposit,
542 lptAMMBalance,
543 lpTokensDeposit,
544 tfee,
-
545 true);
+
545 IsDeposit::Yes);
546
547 if (lpTokensDepositActual <= beast::zero)
548 {
@@ -703,264 +703,348 @@ $(function() {
625 return {tesSUCCESS, lptAMMBalance + lpTokensDepositActual};
626}
627
-
631std::pair<TER, STAmount>
-
632AMMDeposit::equalDepositTokens(
-
633 Sandbox& view,
-
634 AccountID const& ammAccount,
-
635 STAmount const& amountBalance,
-
636 STAmount const& amount2Balance,
-
637 STAmount const& lptAMMBalance,
-
638 STAmount const& lpTokensDeposit,
-
639 std::optional<STAmount> const& depositMin,
-
640 std::optional<STAmount> const& deposit2Min,
-
641 std::uint16_t tfee)
-
642{
-
643 try
-
644 {
-
645 auto const frac =
-
646 divide(lpTokensDeposit, lptAMMBalance, lptAMMBalance.issue());
-
647 return deposit(
-
648 view,
-
649 ammAccount,
-
650 amountBalance,
-
651 multiply(amountBalance, frac, amountBalance.issue()),
-
652 multiply(amount2Balance, frac, amount2Balance.issue()),
-
653 lptAMMBalance,
-
654 lpTokensDeposit,
-
655 depositMin,
-
656 deposit2Min,
-
657 std::nullopt,
-
658 tfee);
-
659 }
-
660 catch (std::exception const& e)
-
661 {
-
662 // LCOV_EXCL_START
-
663 JLOG(j_.error()) << "AMMDeposit::equalDepositTokens exception "
-
664 << e.what();
-
665 return {tecINTERNAL, STAmount{}};
-
666 // LCOV_EXCL_STOP
-
667 }
-
668}
-
669
-
698std::pair<TER, STAmount>
-
699AMMDeposit::equalDepositLimit(
-
700 Sandbox& view,
-
701 AccountID const& ammAccount,
-
702 STAmount const& amountBalance,
-
703 STAmount const& amount2Balance,
-
704 STAmount const& lptAMMBalance,
-
705 STAmount const& amount,
-
706 STAmount const& amount2,
-
707 std::optional<STAmount> const& lpTokensDepositMin,
-
708 std::uint16_t tfee)
-
709{
-
710 auto frac = Number{amount} / amountBalance;
-
711 auto tokens = toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac);
-
712 if (tokens == beast::zero)
-
713 return {tecAMM_FAILED, STAmount{}};
-
714 auto const amount2Deposit = amount2Balance * frac;
-
715 if (amount2Deposit <= amount2)
-
716 return deposit(
-
717 view,
-
718 ammAccount,
-
719 amountBalance,
-
720 amount,
-
721 toSTAmount(amount2Balance.issue(), amount2Deposit),
-
722 lptAMMBalance,
-
723 tokens,
-
724 std::nullopt,
-
725 std::nullopt,
-
726 lpTokensDepositMin,
-
727 tfee);
-
728 frac = Number{amount2} / amount2Balance;
-
729 tokens = toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac);
-
730 if (tokens == beast::zero)
-
731 return {tecAMM_FAILED, STAmount{}};
-
732 auto const amountDeposit = amountBalance * frac;
-
733 if (amountDeposit <= amount)
-
734 return deposit(
-
735 view,
-
736 ammAccount,
-
737 amountBalance,
-
738 toSTAmount(amountBalance.issue(), amountDeposit),
-
739 amount2,
-
740 lptAMMBalance,
-
741 tokens,
-
742 std::nullopt,
-
743 std::nullopt,
-
744 lpTokensDepositMin,
-
745 tfee);
-
746 return {tecAMM_FAILED, STAmount{}};
-
747}
-
748
-
757std::pair<TER, STAmount>
-
758AMMDeposit::singleDeposit(
-
759 Sandbox& view,
-
760 AccountID const& ammAccount,
-
761 STAmount const& amountBalance,
-
762 STAmount const& lptAMMBalance,
-
763 STAmount const& amount,
-
764 std::optional<STAmount> const& lpTokensDepositMin,
-
765 std::uint16_t tfee)
-
766{
-
767 auto const tokens = lpTokensIn(amountBalance, amount, lptAMMBalance, tfee);
-
768 if (tokens == beast::zero)
-
769 return {tecAMM_FAILED, STAmount{}};
-
770 return deposit(
-
771 view,
-
772 ammAccount,
-
773 amountBalance,
-
774 amount,
-
775 std::nullopt,
-
776 lptAMMBalance,
-
777 tokens,
-
778 std::nullopt,
-
779 std::nullopt,
-
780 lpTokensDepositMin,
-
781 tfee);
-
782}
-
783
-
791std::pair<TER, STAmount>
-
792AMMDeposit::singleDepositTokens(
-
793 Sandbox& view,
-
794 AccountID const& ammAccount,
-
795 STAmount const& amountBalance,
-
796 STAmount const& amount,
-
797 STAmount const& lptAMMBalance,
-
798 STAmount const& lpTokensDeposit,
-
799 std::uint16_t tfee)
-
800{
-
801 auto const amountDeposit =
-
802 ammAssetIn(amountBalance, lptAMMBalance, lpTokensDeposit, tfee);
-
803 if (amountDeposit > amount)
-
804 return {tecAMM_FAILED, STAmount{}};
-
805 return deposit(
-
806 view,
-
807 ammAccount,
-
808 amountBalance,
-
809 amountDeposit,
-
810 std::nullopt,
-
811 lptAMMBalance,
-
812 lpTokensDeposit,
-
813 std::nullopt,
-
814 std::nullopt,
-
815 std::nullopt,
-
816 tfee);
-
817}
-
818
-
844std::pair<TER, STAmount>
-
845AMMDeposit::singleDepositEPrice(
-
846 Sandbox& view,
-
847 AccountID const& ammAccount,
-
848 STAmount const& amountBalance,
-
849 STAmount const& amount,
-
850 STAmount const& lptAMMBalance,
-
851 STAmount const& ePrice,
-
852 std::uint16_t tfee)
-
853{
-
854 if (amount != beast::zero)
-
855 {
-
856 auto const tokens =
-
857 lpTokensIn(amountBalance, amount, lptAMMBalance, tfee);
-
858 if (tokens <= beast::zero)
-
859 return {tecAMM_FAILED, STAmount{}};
-
860 auto const ep = Number{amount} / tokens;
-
861 if (ep <= ePrice)
-
862 return deposit(
-
863 view,
-
864 ammAccount,
-
865 amountBalance,
-
866 amount,
-
867 std::nullopt,
-
868 lptAMMBalance,
-
869 tokens,
-
870 std::nullopt,
-
871 std::nullopt,
-
872 std::nullopt,
-
873 tfee);
-
874 }
-
875
-
876 // LPTokens is asset out => E = b / t
-
877 // substituting t in formula (3) as b/E:
-
878 // b/E = T * [b/B - sqrt(t2**2 + b/(f1*B)) + t2]/
-
879 // [1 + sqrt(t2**2 + b/(f1*B)) -t2] (A)
-
880 // where f1 = 1 - fee, f2 = (1 - fee/2)/f1
-
881 // Let R = b/(f1*B), then b/B = f1*R and b = R*f1*B
-
882 // Then (A) is
-
883 // R*f1*B = E*T*[R*f1 -sqrt(f2**2 + R) + f2]/[1 + sqrt(f2**2 + R) - f2] =>
-
884 // Let c = f1*B/(E*T) =>
-
885 // R*c*(1 + sqrt(f2**2 + R) + f2) = R*f1 - sqrt(f2**2 + R) - f2 =>
-
886 // (R*c + 1)*sqrt(f2**2 + R) = R*(f1 + c*f2 - c) + f2 =>
-
887 // Let d = f1 + c*f2 - c =>
-
888 // (R*c + 1)*sqrt(f2**2 + R) = R*d + f2 =>
-
889 // (R*c + 1)**2 * (f2**2 + R) = (R*d + f2)**2 =>
-
890 // (R*c)**2 + R*((c*f2)**2 + 2*c - d**2) + 2*c*f2**2 + 1 -2*d*f2 = 0 =>
-
891 // a1 = c**2, b1 = (c*f2)**2 + 2*c - d**2, c1 = 2*c*f2**2 + 1 - 2*d*f2
-
892 // R = (-b1 + sqrt(b1**2 + 4*a1*c1))/(2*a1)
-
893 auto const f1 = feeMult(tfee);
-
894 auto const f2 = feeMultHalf(tfee) / f1;
-
895 auto const c = f1 * amountBalance / (ePrice * lptAMMBalance);
-
896 auto const d = f1 + c * f2 - c;
-
897 auto const a1 = c * c;
-
898 auto const b1 = c * c * f2 * f2 + 2 * c - d * d;
-
899 auto const c1 = 2 * c * f2 * f2 + 1 - 2 * d * f2;
-
900 auto const amountDeposit = toSTAmount(
-
901 amountBalance.issue(),
-
902 f1 * amountBalance * solveQuadraticEq(a1, b1, c1));
-
903 if (amountDeposit <= beast::zero)
-
904 return {tecAMM_FAILED, STAmount{}};
-
905 auto const tokens =
-
906 toSTAmount(lptAMMBalance.issue(), amountDeposit / ePrice);
-
907 return deposit(
-
908 view,
-
909 ammAccount,
-
910 amountBalance,
-
911 amountDeposit,
-
912 std::nullopt,
-
913 lptAMMBalance,
-
914 tokens,
-
915 std::nullopt,
-
916 std::nullopt,
-
917 std::nullopt,
-
918 tfee);
-
919}
-
920
-
921std::pair<TER, STAmount>
-
922AMMDeposit::equalDepositInEmptyState(
-
923 Sandbox& view,
-
924 AccountID const& ammAccount,
-
925 STAmount const& amount,
-
926 STAmount const& amount2,
-
927 Issue const& lptIssue,
-
928 std::uint16_t tfee)
-
929{
-
930 return deposit(
-
931 view,
-
932 ammAccount,
-
933 amount,
-
934 amount,
-
935 amount2,
-
936 STAmount{lptIssue, 0},
-
937 ammLPTokens(amount, amount2, lptIssue),
-
938 std::nullopt,
-
939 std::nullopt,
-
940 std::nullopt,
-
941 tfee);
-
942}
+
628static STAmount
+
629adjustLPTokensOut(
+
630 Rules const& rules,
+
631 STAmount const& lptAMMBalance,
+
632 STAmount const& lpTokensDeposit)
+
633{
+
634 if (!rules.enabled(fixAMMv1_3))
+
635 return lpTokensDeposit;
+
636 return adjustLPTokens(lptAMMBalance, lpTokensDeposit, IsDeposit::Yes);
+
637}
+
638
+
642std::pair<TER, STAmount>
+
643AMMDeposit::equalDepositTokens(
+
644 Sandbox& view,
+
645 AccountID const& ammAccount,
+
646 STAmount const& amountBalance,
+
647 STAmount const& amount2Balance,
+
648 STAmount const& lptAMMBalance,
+
649 STAmount const& lpTokensDeposit,
+
650 std::optional<STAmount> const& depositMin,
+
651 std::optional<STAmount> const& deposit2Min,
+
652 std::uint16_t tfee)
+
653{
+
654 try
+
655 {
+
656 auto const tokensAdj =
+
657 adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
+
658 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
+
659 return {tecAMM_INVALID_TOKENS, STAmount{}};
+
660 auto const frac =
+
661 divide(tokensAdj, lptAMMBalance, lptAMMBalance.issue());
+
662 // amounts factor in the adjusted tokens
+
663 auto const amountDeposit =
+
664 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
+
665 auto const amount2Deposit =
+
666 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
+
667 return deposit(
+
668 view,
+
669 ammAccount,
+
670 amountBalance,
+
671 amountDeposit,
+
672 amount2Deposit,
+
673 lptAMMBalance,
+
674 tokensAdj,
+
675 depositMin,
+
676 deposit2Min,
+
677 std::nullopt,
+
678 tfee);
+
679 }
+
680 catch (std::exception const& e)
+
681 {
+
682 // LCOV_EXCL_START
+
683 JLOG(j_.error()) << "AMMDeposit::equalDepositTokens exception "
+
684 << e.what();
+
685 return {tecINTERNAL, STAmount{}};
+
686 // LCOV_EXCL_STOP
+
687 }
+
688}
+
689
+
718std::pair<TER, STAmount>
+
719AMMDeposit::equalDepositLimit(
+
720 Sandbox& view,
+
721 AccountID const& ammAccount,
+
722 STAmount const& amountBalance,
+
723 STAmount const& amount2Balance,
+
724 STAmount const& lptAMMBalance,
+
725 STAmount const& amount,
+
726 STAmount const& amount2,
+
727 std::optional<STAmount> const& lpTokensDepositMin,
+
728 std::uint16_t tfee)
+
729{
+
730 auto frac = Number{amount} / amountBalance;
+
731 auto tokensAdj =
+
732 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
+
733 if (tokensAdj == beast::zero)
+
734 {
+
735 if (!view.rules().enabled(fixAMMv1_3))
+
736 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
+
737 else
+
738 return {tecAMM_INVALID_TOKENS, STAmount{}};
+
739 }
+
740 // factor in the adjusted tokens
+
741 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
+
742 auto const amount2Deposit =
+
743 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
+
744 if (amount2Deposit <= amount2)
+
745 return deposit(
+
746 view,
+
747 ammAccount,
+
748 amountBalance,
+
749 amount,
+
750 amount2Deposit,
+
751 lptAMMBalance,
+
752 tokensAdj,
+
753 std::nullopt,
+
754 std::nullopt,
+
755 lpTokensDepositMin,
+
756 tfee);
+
757 frac = Number{amount2} / amount2Balance;
+
758 tokensAdj =
+
759 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
+
760 if (tokensAdj == beast::zero)
+
761 {
+
762 if (!view.rules().enabled(fixAMMv1_3))
+
763 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
+
764 else
+
765 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
+
766 }
+
767 // factor in the adjusted tokens
+
768 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
+
769 auto const amountDeposit =
+
770 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
+
771 if (amountDeposit <= amount)
+
772 return deposit(
+
773 view,
+
774 ammAccount,
+
775 amountBalance,
+
776 amountDeposit,
+
777 amount2,
+
778 lptAMMBalance,
+
779 tokensAdj,
+
780 std::nullopt,
+
781 std::nullopt,
+
782 lpTokensDepositMin,
+
783 tfee);
+
784 return {tecAMM_FAILED, STAmount{}};
+
785}
+
786
+
795std::pair<TER, STAmount>
+
796AMMDeposit::singleDeposit(
+
797 Sandbox& view,
+
798 AccountID const& ammAccount,
+
799 STAmount const& amountBalance,
+
800 STAmount const& lptAMMBalance,
+
801 STAmount const& amount,
+
802 std::optional<STAmount> const& lpTokensDepositMin,
+
803 std::uint16_t tfee)
+
804{
+
805 auto const tokens = adjustLPTokensOut(
+
806 view.rules(),
+
807 lptAMMBalance,
+
808 lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
+
809 if (tokens == beast::zero)
+
810 {
+
811 if (!view.rules().enabled(fixAMMv1_3))
+
812 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
+
813 else
+
814 return {tecAMM_INVALID_TOKENS, STAmount{}};
+
815 }
+
816 // factor in the adjusted tokens
+
817 auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
+
818 view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
+
819 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
+
820 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
+
821 return deposit(
+
822 view,
+
823 ammAccount,
+
824 amountBalance,
+
825 amountDepositAdj,
+
826 std::nullopt,
+
827 lptAMMBalance,
+
828 tokensAdj,
+
829 std::nullopt,
+
830 std::nullopt,
+
831 lpTokensDepositMin,
+
832 tfee);
+
833}
+
834
+
842std::pair<TER, STAmount>
+
843AMMDeposit::singleDepositTokens(
+
844 Sandbox& view,
+
845 AccountID const& ammAccount,
+
846 STAmount const& amountBalance,
+
847 STAmount const& amount,
+
848 STAmount const& lptAMMBalance,
+
849 STAmount const& lpTokensDeposit,
+
850 std::uint16_t tfee)
+
851{
+
852 auto const tokensAdj =
+
853 adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
+
854 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
+
855 return {tecAMM_INVALID_TOKENS, STAmount{}};
+
856 // the adjusted tokens are factored in
+
857 auto const amountDeposit =
+
858 ammAssetIn(amountBalance, lptAMMBalance, tokensAdj, tfee);
+
859 if (amountDeposit > amount)
+
860 return {tecAMM_FAILED, STAmount{}};
+
861 return deposit(
+
862 view,
+
863 ammAccount,
+
864 amountBalance,
+
865 amountDeposit,
+
866 std::nullopt,
+
867 lptAMMBalance,
+
868 tokensAdj,
+
869 std::nullopt,
+
870 std::nullopt,
+
871 std::nullopt,
+
872 tfee);
+
873}
+
874
+
900std::pair<TER, STAmount>
+
901AMMDeposit::singleDepositEPrice(
+
902 Sandbox& view,
+
903 AccountID const& ammAccount,
+
904 STAmount const& amountBalance,
+
905 STAmount const& amount,
+
906 STAmount const& lptAMMBalance,
+
907 STAmount const& ePrice,
+
908 std::uint16_t tfee)
+
909{
+
910 if (amount != beast::zero)
+
911 {
+
912 auto const tokens = adjustLPTokensOut(
+
913 view.rules(),
+
914 lptAMMBalance,
+
915 lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
+
916 if (tokens <= beast::zero)
+
917 {
+
918 if (!view.rules().enabled(fixAMMv1_3))
+
919 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
+
920 else
+
921 return {tecAMM_INVALID_TOKENS, STAmount{}};
+
922 }
+
923 // factor in the adjusted tokens
+
924 auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
+
925 view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
+
926 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
+
927 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
+
928 auto const ep = Number{amountDepositAdj} / tokensAdj;
+
929 if (ep <= ePrice)
+
930 return deposit(
+
931 view,
+
932 ammAccount,
+
933 amountBalance,
+
934 amountDepositAdj,
+
935 std::nullopt,
+
936 lptAMMBalance,
+
937 tokensAdj,
+
938 std::nullopt,
+
939 std::nullopt,
+
940 std::nullopt,
+
941 tfee);
+
942 }
943
-
944} // namespace ripple
+
944 // LPTokens is asset out => E = b / t
+
945 // substituting t in formula (3) as b/E:
+
946 // b/E = T * [b/B - sqrt(t2**2 + b/(f1*B)) + t2]/
+
947 // [1 + sqrt(t2**2 + b/(f1*B)) -t2] (A)
+
948 // where f1 = 1 - fee, f2 = (1 - fee/2)/f1
+
949 // Let R = b/(f1*B), then b/B = f1*R and b = R*f1*B
+
950 // Then (A) is
+
951 // R*f1*B = E*T*[R*f1 -sqrt(f2**2 + R) + f2]/[1 + sqrt(f2**2 + R) - f2] =>
+
952 // Let c = f1*B/(E*T) =>
+
953 // R*c*(1 + sqrt(f2**2 + R) + f2) = R*f1 - sqrt(f2**2 + R) - f2 =>
+
954 // (R*c + 1)*sqrt(f2**2 + R) = R*(f1 + c*f2 - c) + f2 =>
+
955 // Let d = f1 + c*f2 - c =>
+
956 // (R*c + 1)*sqrt(f2**2 + R) = R*d + f2 =>
+
957 // (R*c + 1)**2 * (f2**2 + R) = (R*d + f2)**2 =>
+
958 // (R*c)**2 + R*((c*f2)**2 + 2*c - d**2) + 2*c*f2**2 + 1 -2*d*f2 = 0 =>
+
959 // a1 = c**2, b1 = (c*f2)**2 + 2*c - d**2, c1 = 2*c*f2**2 + 1 - 2*d*f2
+
960 // R = (-b1 + sqrt(b1**2 + 4*a1*c1))/(2*a1)
+
961 auto const f1 = feeMult(tfee);
+
962 auto const f2 = feeMultHalf(tfee) / f1;
+
963 auto const c = f1 * amountBalance / (ePrice * lptAMMBalance);
+
964 auto const d = f1 + c * f2 - c;
+
965 auto const a1 = c * c;
+
966 auto const b1 = c * c * f2 * f2 + 2 * c - d * d;
+
967 auto const c1 = 2 * c * f2 * f2 + 1 - 2 * d * f2;
+
968 auto amtNoRoundCb = [&] {
+
969 return f1 * amountBalance * solveQuadraticEq(a1, b1, c1);
+
970 };
+
971 auto amtProdCb = [&] { return f1 * solveQuadraticEq(a1, b1, c1); };
+
972 auto const amountDeposit = getRoundedAsset(
+
973 view.rules(), amtNoRoundCb, amountBalance, amtProdCb, IsDeposit::Yes);
+
974 if (amountDeposit <= beast::zero)
+
975 return {tecAMM_FAILED, STAmount{}};
+
976 auto tokNoRoundCb = [&] { return amountDeposit / ePrice; };
+
977 auto tokProdCb = [&] { return amountDeposit / ePrice; };
+
978 auto const tokens = getRoundedLPTokens(
+
979 view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::Yes);
+
980 // factor in the adjusted tokens
+
981 auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
+
982 view.rules(),
+
983 amountBalance,
+
984 amountDeposit,
+
985 lptAMMBalance,
+
986 tokens,
+
987 tfee);
+
988 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
+
989 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
+
990
+
991 return deposit(
+
992 view,
+
993 ammAccount,
+
994 amountBalance,
+
995 amountDepositAdj,
+
996 std::nullopt,
+
997 lptAMMBalance,
+
998 tokensAdj,
+
999 std::nullopt,
+
1000 std::nullopt,
+
1001 std::nullopt,
+
1002 tfee);
+
1003}
+
1004
+
1005std::pair<TER, STAmount>
+
1006AMMDeposit::equalDepositInEmptyState(
+
1007 Sandbox& view,
+
1008 AccountID const& ammAccount,
+
1009 STAmount const& amount,
+
1010 STAmount const& amount2,
+
1011 Issue const& lptIssue,
+
1012 std::uint16_t tfee)
+
1013{
+
1014 return deposit(
+
1015 view,
+
1016 ammAccount,
+
1017 amount,
+
1018 amount,
+
1019 amount2,
+
1020 STAmount{lptIssue, 0},
+
1021 ammLPTokens(amount, amount2, lptIssue),
+
1022 std::nullopt,
+
1023 std::nullopt,
+
1024 std::nullopt,
+
1025 tfee);
+
1026}
+
1027
+
1028} // namespace ripple
Stream error() const
Definition: Journal.h:346
Stream debug() const
Definition: Journal.h:328
std::pair< TER, bool > applyGuts(Sandbox &view)
Definition: AMMDeposit.cpp:368
-
std::pair< TER, STAmount > equalDepositLimit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &amount2, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Equal asset deposit (Asset1In, Asset2In) with the constraint on the maximum amount of both assets tha...
Definition: AMMDeposit.cpp:699
-
std::pair< TER, STAmount > singleDepositEPrice(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &ePrice, std::uint16_t tfee)
Single asset deposit (Asset1In, EPrice) with two constraints.
Definition: AMMDeposit.cpp:845
+
std::pair< TER, STAmount > equalDepositLimit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &amount2, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Equal asset deposit (Asset1In, Asset2In) with the constraint on the maximum amount of both assets tha...
Definition: AMMDeposit.cpp:719
+
std::pair< TER, STAmount > singleDepositEPrice(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &ePrice, std::uint16_t tfee)
Single asset deposit (Asset1In, EPrice) with two constraints.
Definition: AMMDeposit.cpp:901
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMDeposit.cpp:166
-
std::pair< TER, STAmount > singleDeposit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Single asset deposit (Asset1In) by the amount.
Definition: AMMDeposit.cpp:758
+
std::pair< TER, STAmount > singleDeposit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Single asset deposit (Asset1In) by the amount.
Definition: AMMDeposit.cpp:796
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMDeposit.cpp:33
-
std::pair< TER, STAmount > equalDepositTokens(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::optional< STAmount > const &depositMin, std::optional< STAmount > const &deposit2Min, std::uint16_t tfee)
Equal asset deposit (LPTokens) for the specified share of the AMM instance pools.
Definition: AMMDeposit.cpp:632
+
std::pair< TER, STAmount > equalDepositTokens(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::optional< STAmount > const &depositMin, std::optional< STAmount > const &deposit2Min, std::uint16_t tfee)
Equal asset deposit (LPTokens) for the specified share of the AMM instance pools.
Definition: AMMDeposit.cpp:643
TER doApply() override
Definition: AMMDeposit.cpp:483
-
std::pair< TER, STAmount > equalDepositInEmptyState(Sandbox &view, AccountID const &ammAccount, STAmount const &amount, STAmount const &amount2, Issue const &lptIssue, std::uint16_t tfee)
Equal deposit in empty AMM state (LP tokens balance is 0)
Definition: AMMDeposit.cpp:922
-
std::pair< TER, STAmount > singleDepositTokens(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::uint16_t tfee)
Single asset deposit (Asset1In, LPTokens) by the tokens.
Definition: AMMDeposit.cpp:792
+
std::pair< TER, STAmount > equalDepositInEmptyState(Sandbox &view, AccountID const &ammAccount, STAmount const &amount, STAmount const &amount2, Issue const &lptIssue, std::uint16_t tfee)
Equal deposit in empty AMM state (LP tokens balance is 0)
+
std::pair< TER, STAmount > singleDepositTokens(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::uint16_t tfee)
Single asset deposit (Asset1In, LPTokens) by the tokens.
Definition: AMMDeposit.cpp:843
std::pair< TER, STAmount > deposit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amountDeposit, std::optional< STAmount > const &amount2Deposit, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::optional< STAmount > const &depositMin, std::optional< STAmount > const &deposit2Min, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Deposit requested assets and token amount into LP account.
Definition: AMMDeposit.cpp:497
RawView & rawView()
Definition: ApplyContext.h:91
ApplyView & view()
Definition: ApplyContext.h:78
@@ -970,6 +1054,7 @@ $(function() {
Definition: Number.h:36
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Rules const & rules() const =0
Returns the tx processing rules.
+
Rules controlling protocol behavior.
Definition: Rules.h:38
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
Definition: STAmount.h:50
Issue const & issue() const
Definition: STAmount.h:496
@@ -995,29 +1080,30 @@ $(function() {
constexpr std::uint32_t tfSingleAsset
Definition: TxFlags.h:217
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition: AMMCore.cpp:95
STAmount divide(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:93
-
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
Definition: AMMHelpers.cpp:41
+
std::pair< STAmount, STAmount > adjustAssetInByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
Definition: AMMHelpers.cpp:349
@ fhZERO_IF_FROZEN
Definition: View.h:78
@ fhIGNORE_FREEZE
Definition: View.h:78
bool isXRP(AccountID const &c)
Definition: AccountID.h:91
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition: View.cpp:215
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
Definition: AMMUtils.cpp:178
-
Number solveQuadraticEq(Number const &a, Number const &b, Number const &c)
Positive solution for quadratic equation: x = (-b + sqrt(b**2 + 4*a*c))/(2*a)
Definition: AMMHelpers.cpp:220
+
Number solveQuadraticEq(Number const &a, Number const &b, Number const &c)
Positive solution for quadratic equation: x = (-b + sqrt(b**2 + 4*a*c))/(2*a)
Definition: AMMHelpers.cpp:264
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account)
Check if the account lacks required authorization.
Definition: View.cpp:2288
-
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
constexpr std::uint32_t tfLimitLPToken
Definition: TxFlags.h:220
-
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
Definition: AMMHelpers.cpp:67
-
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:53
+
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
Definition: AMMHelpers.cpp:80
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition: AMMCore.cpp:129
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:91
-
std::tuple< STAmount, std::optional< STAmount >, STAmount > adjustAmountsByLPTokens(STAmount const &amountBalance, STAmount const &amount, std::optional< STAmount > const &amount2, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee, bool isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
Definition: AMMHelpers.cpp:147
constexpr std::uint32_t tfOneAssetLPToken
Definition: TxFlags.h:219
+
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
Definition: AMMHelpers.cpp:311
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition: View.cpp:249
+
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
Definition: AMMHelpers.cpp:173
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
Definition: AMMCore.h:110
void initializeFeeAuctionVote(ApplyView &view, std::shared_ptr< SLE > &ammSle, AccountID const &account, Issue const &lptIssue, std::uint16_t tfee)
Initialize Auction and Voting slots and set the trading/discounted fee.
Definition: AMMUtils.cpp:339
constexpr std::uint32_t tfTwoAsset
Definition: TxFlags.h:218
STAmount ammLPHolds(ReadView const &view, Currency const &cur1, Currency const &cur2, AccountID const &ammAccount, AccountID const &lpAccount, beast::Journal const j)
Get the balance of LP tokens.
Definition: AMMUtils.cpp:112
+
std::tuple< STAmount, std::optional< STAmount >, STAmount > adjustAmountsByLPTokens(STAmount const &amountBalance, STAmount const &amount, std::optional< STAmount > const &amount2, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee, IsDeposit isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
Definition: AMMHelpers.cpp:187
constexpr std::uint32_t tfDepositSubTx
Definition: TxFlags.h:225
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:160
+
static STAmount adjustLPTokensOut(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit)
Definition: AMMDeposit.cpp:629
@ Yes
@ tecINSUF_RESERVE_LINE
Definition: TER.h:288
@ tecFROZEN
Definition: TER.h:303
@@ -1028,6 +1114,7 @@ $(function() {
@ tecAMM_FAILED
Definition: TER.h:330
@ tecAMM_INVALID_TOKENS
Definition: TER.h:331
constexpr std::uint32_t tfLPToken
Definition: TxFlags.h:214
+
@ Yes
@ tesSUCCESS
Definition: TER.h:244
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition: AMMCore.cpp:80
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:386
@@ -1038,9 +1125,12 @@ $(function() {
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
constexpr std::uint32_t tfTwoAssetIfEmpty
Definition: TxFlags.h:221
Number feeMultHalf(std::uint16_t tfee)
Get fee multiplier (1 - tfee / 2) @tfee trading fee in basis points.
Definition: AMMCore.h:119
+
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
Definition: AMMHelpers.cpp:45
@ terNO_AMM
Definition: TER.h:227
TERSubset< CanCvtToTER > TER
Definition: TER.h:643
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition: AMMCore.h:31
+
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition: AMMHelpers.h:678
+
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
Definition: AMMHelpers.cpp:401
TER accountSend(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Calls static accountSendIOU if saAmount represents Issue.
Definition: View.cpp:1998
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:603
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition: View.cpp:617
diff --git a/AMMDeposit_8h_source.html b/AMMDeposit_8h_source.html index 753ba23c9d..d3e44250c2 100644 --- a/AMMDeposit_8h_source.html +++ b/AMMDeposit_8h_source.html @@ -209,16 +209,16 @@ $(function() {
247#endif // RIPPLE_TX_AMMDEPOSIT_H_INCLUDED
AMMDeposit implements AMM deposit Transactor.
Definition: AMMDeposit.h:63
std::pair< TER, bool > applyGuts(Sandbox &view)
Definition: AMMDeposit.cpp:368
-
std::pair< TER, STAmount > equalDepositLimit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &amount2, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Equal asset deposit (Asset1In, Asset2In) with the constraint on the maximum amount of both assets tha...
Definition: AMMDeposit.cpp:699
-
std::pair< TER, STAmount > singleDepositEPrice(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &ePrice, std::uint16_t tfee)
Single asset deposit (Asset1In, EPrice) with two constraints.
Definition: AMMDeposit.cpp:845
+
std::pair< TER, STAmount > equalDepositLimit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &amount2, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Equal asset deposit (Asset1In, Asset2In) with the constraint on the maximum amount of both assets tha...
Definition: AMMDeposit.cpp:719
+
std::pair< TER, STAmount > singleDepositEPrice(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &ePrice, std::uint16_t tfee)
Single asset deposit (Asset1In, EPrice) with two constraints.
Definition: AMMDeposit.cpp:901
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMDeposit.cpp:166
-
std::pair< TER, STAmount > singleDeposit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Single asset deposit (Asset1In) by the amount.
Definition: AMMDeposit.cpp:758
+
std::pair< TER, STAmount > singleDeposit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Single asset deposit (Asset1In) by the amount.
Definition: AMMDeposit.cpp:796
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMDeposit.cpp:33
-
std::pair< TER, STAmount > equalDepositTokens(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::optional< STAmount > const &depositMin, std::optional< STAmount > const &deposit2Min, std::uint16_t tfee)
Equal asset deposit (LPTokens) for the specified share of the AMM instance pools.
Definition: AMMDeposit.cpp:632
+
std::pair< TER, STAmount > equalDepositTokens(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::optional< STAmount > const &depositMin, std::optional< STAmount > const &deposit2Min, std::uint16_t tfee)
Equal asset deposit (LPTokens) for the specified share of the AMM instance pools.
Definition: AMMDeposit.cpp:643
static constexpr ConsequencesFactoryType ConsequencesFactory
Definition: AMMDeposit.h:65
TER doApply() override
Definition: AMMDeposit.cpp:483
-
std::pair< TER, STAmount > equalDepositInEmptyState(Sandbox &view, AccountID const &ammAccount, STAmount const &amount, STAmount const &amount2, Issue const &lptIssue, std::uint16_t tfee)
Equal deposit in empty AMM state (LP tokens balance is 0)
Definition: AMMDeposit.cpp:922
-
std::pair< TER, STAmount > singleDepositTokens(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::uint16_t tfee)
Single asset deposit (Asset1In, LPTokens) by the tokens.
Definition: AMMDeposit.cpp:792
+
std::pair< TER, STAmount > equalDepositInEmptyState(Sandbox &view, AccountID const &ammAccount, STAmount const &amount, STAmount const &amount2, Issue const &lptIssue, std::uint16_t tfee)
Equal deposit in empty AMM state (LP tokens balance is 0)
+
std::pair< TER, STAmount > singleDepositTokens(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::uint16_t tfee)
Single asset deposit (Asset1In, LPTokens) by the tokens.
Definition: AMMDeposit.cpp:843
std::pair< TER, STAmount > deposit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amountDeposit, std::optional< STAmount > const &amount2Deposit, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::optional< STAmount > const &depositMin, std::optional< STAmount > const &deposit2Min, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Deposit requested assets and token amount into LP account.
Definition: AMMDeposit.cpp:497
AMMDeposit(ApplyContext &ctx)
Definition: AMMDeposit.h:67
State information when applying a tx.
Definition: ApplyContext.h:37
diff --git a/AMMExtended__test_8cpp_source.html b/AMMExtended__test_8cpp_source.html index a941b6d6dc..f552386fc9 100644 --- a/AMMExtended__test_8cpp_source.html +++ b/AMMExtended__test_8cpp_source.html @@ -1526,7 +1526,7 @@ $(function() {
1451 using namespace jtx;
1452 FeatureBitset const all{supported_amendments()};
1453 testRmFundedOffer(all);
-
1454 testRmFundedOffer(all - fixAMMv1_1);
+
1454 testRmFundedOffer(all - fixAMMv1_1 - fixAMMv1_3);
1455 testEnforceNoRipple(all);
1456 testFillModes(all);
1457 testOfferCrossWithXRP(all);
@@ -1540,7 +1540,7 @@ $(function() {
1465 testOfferCreateThenCross(all);
1466 testSellFlagExceedLimit(all);
1467 testGatewayCrossCurrency(all);
-
1468 testGatewayCrossCurrency(all - fixAMMv1_1);
+
1468 testGatewayCrossCurrency(all - fixAMMv1_1 - fixAMMv1_3);
1469 testBridgedCross(all);
1470 testSellWithFillOrKill(all);
1471 testTransferRateOffer(all);
@@ -1548,7 +1548,7 @@ $(function() {
1473 testBadPathAssert(all);
1474 testSellFlagBasic(all);
1475 testDirectToDirectPath(all);
-
1476 testDirectToDirectPath(all - fixAMMv1_1);
+
1476 testDirectToDirectPath(all - fixAMMv1_1 - fixAMMv1_3);
1477 testRequireAuth(all);
1478 testMissingAuth(all);
1479 }
@@ -4134,9 +4134,9 @@ $(function() {
4063 testBookStep(all);
4064 testBookStep(all | ownerPaysFee);
4065 testTransferRate(all | ownerPaysFee);
-
4066 testTransferRate((all - fixAMMv1_1) | ownerPaysFee);
+
4066 testTransferRate((all - fixAMMv1_1 - fixAMMv1_3) | ownerPaysFee);
4067 testTransferRateNoOwnerFee(all);
-
4068 testTransferRateNoOwnerFee(all - fixAMMv1_1);
+
4068 testTransferRateNoOwnerFee(all - fixAMMv1_1 - fixAMMv1_3);
4069 testLimitQuality();
4070 testXRPPathLoop();
4071 }
@@ -4147,7 +4147,7 @@ $(function() {
4076 using namespace jtx;
4077 FeatureBitset const all{supported_amendments()};
4078 testStepLimit(all);
-
4079 testStepLimit(all - fixAMMv1_1);
+
4079 testStepLimit(all - fixAMMv1_1 - fixAMMv1_3);
4080 }
4081
4082 void
@@ -4156,7 +4156,7 @@ $(function() {
4085 using namespace jtx;
4086 FeatureBitset const all{supported_amendments()};
4087 test_convert_all_of_an_asset(all);
-
4088 test_convert_all_of_an_asset(all - fixAMMv1_1);
+
4088 test_convert_all_of_an_asset(all - fixAMMv1_1 - fixAMMv1_3);
4089 }
4090
4091 void
@@ -4247,32 +4247,32 @@ $(function() {
Definition: PathSet.h:167
Definition: PathSet.h:94
Path & push_back(Issue const &iss)
Definition: PathSet.h:134
-
jtx::Account const alice
Definition: AMMTest.h:68
-
jtx::IOU const BTC
Definition: AMMTest.h:73
-
jtx::IOU const USD
Definition: AMMTest.h:70
-
jtx::Account const gw
Definition: AMMTest.h:66
-
jtx::Account const bob
Definition: AMMTest.h:69
-
jtx::IOU const EUR
Definition: AMMTest.h:71
-
jtx::IOU const GBP
Definition: AMMTest.h:72
-
jtx::Account const carol
Definition: AMMTest.h:67
-
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> &&cb, std::optional< std::pair< STAmount, STAmount > > const &pool=std::nullopt, std::uint16_t tfee=0, std::optional< jtx::ter > const &ter=std::nullopt, std::vector< FeatureBitset > const &features={supported_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
Definition: AMMTest.cpp:102
-
Definition: AMMTest.h:93
-
std::tuple< STPathSet, STAmount, STAmount > find_paths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt)
Definition: AMMTest.cpp:239
-
jtx::Env pathTestEnv()
Definition: AMMTest.cpp:166
-
XRPAmount ammCrtFee(jtx::Env &env) const
Definition: AMMTest.cpp:160
-
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Definition: AMMTest.cpp:154
+
jtx::Account const alice
Definition: AMMTest.h:77
+
jtx::IOU const BTC
Definition: AMMTest.h:82
+
jtx::IOU const USD
Definition: AMMTest.h:79
+
jtx::Account const gw
Definition: AMMTest.h:75
+
jtx::Account const bob
Definition: AMMTest.h:78
+
jtx::IOU const EUR
Definition: AMMTest.h:80
+
jtx::IOU const GBP
Definition: AMMTest.h:81
+
jtx::Account const carol
Definition: AMMTest.h:76
+
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> &&cb, std::optional< std::pair< STAmount, STAmount > > const &pool=std::nullopt, std::uint16_t tfee=0, std::optional< jtx::ter > const &ter=std::nullopt, std::vector< FeatureBitset > const &features={supported_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
Definition: AMMTest.cpp:103
+
Definition: AMMTest.h:107
+
std::tuple< STPathSet, STAmount, STAmount > find_paths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt)
Definition: AMMTest.cpp:256
+
jtx::Env pathTestEnv()
Definition: AMMTest.cpp:183
+
XRPAmount ammCrtFee(jtx::Env &env) const
Definition: AMMTest.cpp:177
+
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Definition: AMMTest.cpp:171
Convenience class to test AMM functionality.
Definition: AMM.h:124
-
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
Definition: AMM.cpp:161
-
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:637
-
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
Definition: AMM.cpp:275
-
IOUAmount tokens() const
Definition: AMM.h:337
-
AccountID const & ammAccount() const
Definition: AMM.h:325
-
bool expectTradingFee(std::uint16_t fee) const
Definition: AMM.cpp:312
-
IOUAmount withdraw(std::optional< Account > const &account, std::optional< LPToken > const &tokens, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:537
-
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition: AMM.cpp:232
-
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:411
-
Json::Value bid(BidArg const &arg)
Definition: AMM.cpp:664
-
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.h:273
+
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
Definition: AMM.cpp:166
+
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:642
+
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
Definition: AMM.cpp:280
+
IOUAmount tokens() const
Definition: AMM.h:343
+
AccountID const & ammAccount() const
Definition: AMM.h:331
+
bool expectTradingFee(std::uint16_t fee) const
Definition: AMM.cpp:317
+
IOUAmount withdraw(std::optional< Account > const &account, std::optional< LPToken > const &tokens, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:542
+
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition: AMM.cpp:237
+
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:416
+
Json::Value bid(BidArg const &arg)
Definition: AMM.cpp:669
+
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.h:279
Immutable cryptographic account descriptor.
Definition: Account.h:39
AccountID id() const
Returns the Account ID.
Definition: Account.h:107
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
@@ -4282,7 +4282,7 @@ $(function() {
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:264
Application & app()
Definition: Env.h:261
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition: Env.cpp:447
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition: Env.cpp:179
@@ -4330,7 +4330,7 @@ $(function() {
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
bool same(STPathSet const &st1, Args const &... args)
Definition: TestHelpers.h:165
Json::Value ledgerEntryState(Env &env, Account const &acct_a, Account const &acct_b, std::string const &currency)
-
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition: AMMTest.cpp:36
+
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition: AMMTest.cpp:37
void n_offers(Env &env, std::size_t n, Account const &account, STAmount const &in, STAmount const &out)
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition: rate.cpp:32
STPathElement IPE(Issue const &iss)
Definition: TestHelpers.cpp:81
diff --git a/AMMHelpers_8cpp_source.html b/AMMHelpers_8cpp_source.html index 833826072b..a1bfeb0fa9 100644 --- a/AMMHelpers_8cpp_source.html +++ b/AMMHelpers_8cpp_source.html @@ -105,245 +105,438 @@ $(function() {
27 STAmount const& asset2,
28 Issue const& lptIssue)
29{
-
30 auto const tokens = root2(asset1 * asset2);
-
31 return toSTAmount(lptIssue, tokens);
-
32}
-
33
-
34/*
-
35 * Equation 3:
-
36 * t = T * [(b/B - (sqrt(f2**2 - b/(B*f1)) - f2)) /
-
37 * (1 + sqrt(f2**2 - b/(B*f1)) - f2)]
-
38 * where f1 = 1 - tfee, f2 = (1 - tfee/2)/f1
-
39 */
-
40STAmount
-
41lpTokensIn(
-
42 STAmount const& asset1Balance,
-
43 STAmount const& asset1Deposit,
-
44 STAmount const& lptAMMBalance,
-
45 std::uint16_t tfee)
-
46{
-
47 auto const f1 = feeMult(tfee);
-
48 auto const f2 = feeMultHalf(tfee) / f1;
-
49 Number const r = asset1Deposit / asset1Balance;
-
50 auto const c = root2(f2 * f2 + r / f1) - f2;
-
51 auto const t = lptAMMBalance * (r - c) / (1 + c);
-
52 return toSTAmount(lptAMMBalance.issue(), t);
-
53}
-
54
-
55/* Equation 4 solves equation 3 for b:
-
56 * Let f1 = 1 - tfee, f2 = (1 - tfee/2)/f1, t1 = t/T, t2 = 1 + t1, R = b/B
-
57 * then
-
58 * t1 = [R - sqrt(f2**2 + R/f1) + f2] / [1 + sqrt(f2**2 + R/f1] - f2] =>
-
59 * sqrt(f2**2 + R/f1)*(t1 + 1) = R + f2 + t1*f2 - t1 =>
-
60 * sqrt(f2**2 + R/f1)*t2 = R + t2*f2 - t1 =>
-
61 * sqrt(f2**2 + R/f1) = R/t2 + f2 - t1/t2, let d = f2 - t1/t2 =>
-
62 * sqrt(f2**2 + R/f1) = R/t2 + d =>
-
63 * f2**2 + R/f1 = (R/t2)**2 +2*d*R/t2 + d**2 =>
-
64 * (R/t2)**2 + R*(2*d/t2 - 1/f1) + d**2 - f2**2 = 0
-
65 */
-
66STAmount
-
67ammAssetIn(
-
68 STAmount const& asset1Balance,
-
69 STAmount const& lptAMMBalance,
-
70 STAmount const& lpTokens,
-
71 std::uint16_t tfee)
-
72{
-
73 auto const f1 = feeMult(tfee);
-
74 auto const f2 = feeMultHalf(tfee) / f1;
-
75 auto const t1 = lpTokens / lptAMMBalance;
-
76 auto const t2 = 1 + t1;
-
77 auto const d = f2 - t1 / t2;
-
78 auto const a = 1 / (t2 * t2);
-
79 auto const b = 2 * d / t2 - 1 / f1;
-
80 auto const c = d * d - f2 * f2;
-
81 return toSTAmount(
-
82 asset1Balance.issue(), asset1Balance * solveQuadraticEq(a, b, c));
-
83}
-
84
-
85/* Equation 7:
-
86 * t = T * (c - sqrt(c**2 - 4*R))/2
-
87 * where R = b/B, c = R*fee + 2 - fee
-
88 */
-
89STAmount
-
90lpTokensOut(
-
91 STAmount const& asset1Balance,
-
92 STAmount const& asset1Withdraw,
-
93 STAmount const& lptAMMBalance,
-
94 std::uint16_t tfee)
-
95{
-
96 Number const fr = asset1Withdraw / asset1Balance;
-
97 auto const f1 = getFee(tfee);
-
98 auto const c = fr * f1 + 2 - f1;
-
99 auto const t = lptAMMBalance * (c - root2(c * c - 4 * fr)) / 2;
-
100 return toSTAmount(lptAMMBalance.issue(), t);
-
101}
-
102
-
103/* Equation 8 solves equation 7 for b:
-
104 * c - 2*t/T = sqrt(c**2 - 4*R) =>
-
105 * c**2 - 4*c*t/T + 4*t**2/T**2 = c**2 - 4*R =>
-
106 * -4*c*t/T + 4*t**2/T**2 = -4*R =>
-
107 * -c*t/T + t**2/T**2 = -R -=>
-
108 * substitute c = R*f + 2 - f =>
-
109 * -(t/T)*(R*f + 2 - f) + (t/T)**2 = -R, let t1 = t/T =>
-
110 * -t1*R*f -2*t1 +t1*f +t1**2 = -R =>
-
111 * R = (t1**2 + t1*(f - 2)) / (t1*f - 1)
-
112 */
-
113STAmount
-
114withdrawByTokens(
-
115 STAmount const& assetBalance,
-
116 STAmount const& lptAMMBalance,
-
117 STAmount const& lpTokens,
-
118 std::uint16_t tfee)
-
119{
-
120 auto const f = getFee(tfee);
-
121 Number const t1 = lpTokens / lptAMMBalance;
-
122 auto const b = assetBalance * (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1);
-
123 return toSTAmount(assetBalance.issue(), b);
-
124}
-
125
-
126Number
-
127square(Number const& n)
-
128{
-
129 return n * n;
-
130}
-
131
-
132STAmount
-
133adjustLPTokens(
-
134 STAmount const& lptAMMBalance,
-
135 STAmount const& lpTokens,
-
136 bool isDeposit)
-
137{
-
138 // Force rounding downward to ensure adjusted tokens are less or equal
-
139 // to requested tokens.
-
140 saveNumberRoundMode rm(Number::setround(Number::rounding_mode::downward));
-
141 if (isDeposit)
-
142 return (lptAMMBalance + lpTokens) - lptAMMBalance;
-
143 return (lpTokens - lptAMMBalance) + lptAMMBalance;
-
144}
-
145
-
146std::tuple<STAmount, std::optional<STAmount>, STAmount>
-
147adjustAmountsByLPTokens(
-
148 STAmount const& amountBalance,
-
149 STAmount const& amount,
-
150 std::optional<STAmount> const& amount2,
-
151 STAmount const& lptAMMBalance,
-
152 STAmount const& lpTokens,
-
153 std::uint16_t tfee,
-
154 bool isDeposit)
-
155{
-
156 auto const lpTokensActual =
-
157 adjustLPTokens(lptAMMBalance, lpTokens, isDeposit);
-
158
-
159 if (lpTokensActual == beast::zero)
-
160 {
-
161 auto const amount2Opt =
-
162 amount2 ? std::make_optional(STAmount{}) : std::nullopt;
-
163 return std::make_tuple(STAmount{}, amount2Opt, lpTokensActual);
-
164 }
+
30 // AMM invariant: sqrt(asset1 * asset2) >= LPTokensBalance
+
31 auto const rounding =
+
32 isFeatureEnabled(fixAMMv1_3) ? Number::downward : Number::getround();
+
33 NumberRoundModeGuard g(rounding);
+
34 auto const tokens = root2(asset1 * asset2);
+
35 return toSTAmount(lptIssue, tokens);
+
36}
+
37
+
38/*
+
39 * Equation 3:
+
40 * t = T * [(b/B - (sqrt(f2**2 - b/(B*f1)) - f2)) /
+
41 * (1 + sqrt(f2**2 - b/(B*f1)) - f2)]
+
42 * where f1 = 1 - tfee, f2 = (1 - tfee/2)/f1
+
43 */
+
44STAmount
+
45lpTokensOut(
+
46 STAmount const& asset1Balance,
+
47 STAmount const& asset1Deposit,
+
48 STAmount const& lptAMMBalance,
+
49 std::uint16_t tfee)
+
50{
+
51 auto const f1 = feeMult(tfee);
+
52 auto const f2 = feeMultHalf(tfee) / f1;
+
53 Number const r = asset1Deposit / asset1Balance;
+
54 auto const c = root2(f2 * f2 + r / f1) - f2;
+
55 if (!isFeatureEnabled(fixAMMv1_3))
+
56 {
+
57 auto const t = lptAMMBalance * (r - c) / (1 + c);
+
58 return toSTAmount(lptAMMBalance.issue(), t);
+
59 }
+
60 else
+
61 {
+
62 // minimize tokens out
+
63 auto const frac = (r - c) / (1 + c);
+
64 return multiply(lptAMMBalance, frac, Number::downward);
+
65 }
+
66}
+
67
+
68/* Equation 4 solves equation 3 for b:
+
69 * Let f1 = 1 - tfee, f2 = (1 - tfee/2)/f1, t1 = t/T, t2 = 1 + t1, R = b/B
+
70 * then
+
71 * t1 = [R - sqrt(f2**2 + R/f1) + f2] / [1 + sqrt(f2**2 + R/f1] - f2] =>
+
72 * sqrt(f2**2 + R/f1)*(t1 + 1) = R + f2 + t1*f2 - t1 =>
+
73 * sqrt(f2**2 + R/f1)*t2 = R + t2*f2 - t1 =>
+
74 * sqrt(f2**2 + R/f1) = R/t2 + f2 - t1/t2, let d = f2 - t1/t2 =>
+
75 * sqrt(f2**2 + R/f1) = R/t2 + d =>
+
76 * f2**2 + R/f1 = (R/t2)**2 +2*d*R/t2 + d**2 =>
+
77 * (R/t2)**2 + R*(2*d/t2 - 1/f1) + d**2 - f2**2 = 0
+
78 */
+
79STAmount
+
80ammAssetIn(
+
81 STAmount const& asset1Balance,
+
82 STAmount const& lptAMMBalance,
+
83 STAmount const& lpTokens,
+
84 std::uint16_t tfee)
+
85{
+
86 auto const f1 = feeMult(tfee);
+
87 auto const f2 = feeMultHalf(tfee) / f1;
+
88 auto const t1 = lpTokens / lptAMMBalance;
+
89 auto const t2 = 1 + t1;
+
90 auto const d = f2 - t1 / t2;
+
91 auto const a = 1 / (t2 * t2);
+
92 auto const b = 2 * d / t2 - 1 / f1;
+
93 auto const c = d * d - f2 * f2;
+
94 if (!isFeatureEnabled(fixAMMv1_3))
+
95 {
+
96 return toSTAmount(
+
97 asset1Balance.issue(), asset1Balance * solveQuadraticEq(a, b, c));
+
98 }
+
99 else
+
100 {
+
101 // maximize deposit
+
102 auto const frac = solveQuadraticEq(a, b, c);
+
103 return multiply(asset1Balance, frac, Number::upward);
+
104 }
+
105}
+
106
+
107/* Equation 7:
+
108 * t = T * (c - sqrt(c**2 - 4*R))/2
+
109 * where R = b/B, c = R*fee + 2 - fee
+
110 */
+
111STAmount
+
112lpTokensIn(
+
113 STAmount const& asset1Balance,
+
114 STAmount const& asset1Withdraw,
+
115 STAmount const& lptAMMBalance,
+
116 std::uint16_t tfee)
+
117{
+
118 Number const fr = asset1Withdraw / asset1Balance;
+
119 auto const f1 = getFee(tfee);
+
120 auto const c = fr * f1 + 2 - f1;
+
121 if (!isFeatureEnabled(fixAMMv1_3))
+
122 {
+
123 auto const t = lptAMMBalance * (c - root2(c * c - 4 * fr)) / 2;
+
124 return toSTAmount(lptAMMBalance.issue(), t);
+
125 }
+
126 else
+
127 {
+
128 // maximize tokens in
+
129 auto const frac = (c - root2(c * c - 4 * fr)) / 2;
+
130 return multiply(lptAMMBalance, frac, Number::upward);
+
131 }
+
132}
+
133
+
134/* Equation 8 solves equation 7 for b:
+
135 * c - 2*t/T = sqrt(c**2 - 4*R) =>
+
136 * c**2 - 4*c*t/T + 4*t**2/T**2 = c**2 - 4*R =>
+
137 * -4*c*t/T + 4*t**2/T**2 = -4*R =>
+
138 * -c*t/T + t**2/T**2 = -R -=>
+
139 * substitute c = R*f + 2 - f =>
+
140 * -(t/T)*(R*f + 2 - f) + (t/T)**2 = -R, let t1 = t/T =>
+
141 * -t1*R*f -2*t1 +t1*f +t1**2 = -R =>
+
142 * R = (t1**2 + t1*(f - 2)) / (t1*f - 1)
+
143 */
+
144STAmount
+
145ammAssetOut(
+
146 STAmount const& assetBalance,
+
147 STAmount const& lptAMMBalance,
+
148 STAmount const& lpTokens,
+
149 std::uint16_t tfee)
+
150{
+
151 auto const f = getFee(tfee);
+
152 Number const t1 = lpTokens / lptAMMBalance;
+
153 if (!isFeatureEnabled(fixAMMv1_3))
+
154 {
+
155 auto const b = assetBalance * (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1);
+
156 return toSTAmount(assetBalance.issue(), b);
+
157 }
+
158 else
+
159 {
+
160 // minimize withdraw
+
161 auto const frac = (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1);
+
162 return multiply(assetBalance, frac, Number::downward);
+
163 }
+
164}
165
-
166 if (lpTokensActual < lpTokens)
-
167 {
-
168 bool const ammRoundingEnabled = [&]() {
-
169 if (auto const& rules = getCurrentTransactionRules();
-
170 rules && rules->enabled(fixAMMv1_1))
-
171 return true;
-
172 return false;
-
173 }();
-
174
-
175 // Equal trade
-
176 if (amount2)
-
177 {
-
178 Number const fr = lpTokensActual / lpTokens;
-
179 auto const amountActual = toSTAmount(amount.issue(), fr * amount);
-
180 auto const amount2Actual =
-
181 toSTAmount(amount2->issue(), fr * *amount2);
-
182 if (!ammRoundingEnabled)
-
183 return std::make_tuple(
-
184 amountActual < amount ? amountActual : amount,
-
185 amount2Actual < amount2 ? amount2Actual : amount2,
-
186 lpTokensActual);
-
187 else
-
188 return std::make_tuple(
-
189 amountActual, amount2Actual, lpTokensActual);
-
190 }
-
191
-
192 // Single trade
-
193 auto const amountActual = [&]() {
-
194 if (isDeposit)
-
195 return ammAssetIn(
-
196 amountBalance, lptAMMBalance, lpTokensActual, tfee);
-
197 else if (!ammRoundingEnabled)
-
198 return withdrawByTokens(
-
199 amountBalance, lptAMMBalance, lpTokens, tfee);
-
200 else
-
201 return withdrawByTokens(
-
202 amountBalance, lptAMMBalance, lpTokensActual, tfee);
-
203 }();
-
204 if (!ammRoundingEnabled)
-
205 return amountActual < amount
-
206 ? std::make_tuple(amountActual, std::nullopt, lpTokensActual)
-
207 : std::make_tuple(amount, std::nullopt, lpTokensActual);
-
208 else
-
209 return std::make_tuple(amountActual, std::nullopt, lpTokensActual);
-
210 }
-
211
-
212 XRPL_ASSERT(
-
213 lpTokensActual == lpTokens,
-
214 "ripple::adjustAmountsByLPTokens : LP tokens match actual");
-
215
-
216 return {amount, amount2, lpTokensActual};
-
217}
+
166Number
+
167square(Number const& n)
+
168{
+
169 return n * n;
+
170}
+
171
+
172STAmount
+
173adjustLPTokens(
+
174 STAmount const& lptAMMBalance,
+
175 STAmount const& lpTokens,
+
176 IsDeposit isDeposit)
+
177{
+
178 // Force rounding downward to ensure adjusted tokens are less or equal
+
179 // to requested tokens.
+
180 saveNumberRoundMode rm(Number::setround(Number::rounding_mode::downward));
+
181 if (isDeposit == IsDeposit::Yes)
+
182 return (lptAMMBalance + lpTokens) - lptAMMBalance;
+
183 return (lpTokens - lptAMMBalance) + lptAMMBalance;
+
184}
+
185
+
186std::tuple<STAmount, std::optional<STAmount>, STAmount>
+
187adjustAmountsByLPTokens(
+
188 STAmount const& amountBalance,
+
189 STAmount const& amount,
+
190 std::optional<STAmount> const& amount2,
+
191 STAmount const& lptAMMBalance,
+
192 STAmount const& lpTokens,
+
193 std::uint16_t tfee,
+
194 IsDeposit isDeposit)
+
195{
+
196 // AMMv1_3 amendment adjusts tokens and amounts in deposit/withdraw
+
197 if (isFeatureEnabled(fixAMMv1_3))
+
198 return std::make_tuple(amount, amount2, lpTokens);
+
199
+
200 auto const lpTokensActual =
+
201 adjustLPTokens(lptAMMBalance, lpTokens, isDeposit);
+
202
+
203 if (lpTokensActual == beast::zero)
+
204 {
+
205 auto const amount2Opt =
+
206 amount2 ? std::make_optional(STAmount{}) : std::nullopt;
+
207 return std::make_tuple(STAmount{}, amount2Opt, lpTokensActual);
+
208 }
+
209
+
210 if (lpTokensActual < lpTokens)
+
211 {
+
212 bool const ammRoundingEnabled = [&]() {
+
213 if (auto const& rules = getCurrentTransactionRules();
+
214 rules && rules->enabled(fixAMMv1_1))
+
215 return true;
+
216 return false;
+
217 }();
218
-
219Number
-
220solveQuadraticEq(Number const& a, Number const& b, Number const& c)
-
221{
-
222 return (-b + root2(b * b - 4 * a * c)) / (2 * a);
-
223}
-
224
-
225// Minimize takerGets or takerPays
-
226std::optional<Number>
-
227solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c)
-
228{
-
229 auto const d = b * b - 4 * a * c;
-
230 if (d < 0)
-
231 return std::nullopt;
-
232 // use numerically stable citardauq formula for quadratic equation solution
-
233 // https://people.csail.mit.edu/bkph/articles/Quadratics.pdf
-
234 if (b > 0)
-
235 return (2 * c) / (-b - root2(d));
-
236 else
-
237 return (2 * c) / (-b + root2(d));
-
238}
-
239
-
240} // namespace ripple
+
219 // Equal trade
+
220 if (amount2)
+
221 {
+
222 Number const fr = lpTokensActual / lpTokens;
+
223 auto const amountActual = toSTAmount(amount.issue(), fr * amount);
+
224 auto const amount2Actual =
+
225 toSTAmount(amount2->issue(), fr * *amount2);
+
226 if (!ammRoundingEnabled)
+
227 return std::make_tuple(
+
228 amountActual < amount ? amountActual : amount,
+
229 amount2Actual < amount2 ? amount2Actual : amount2,
+
230 lpTokensActual);
+
231 else
+
232 return std::make_tuple(
+
233 amountActual, amount2Actual, lpTokensActual);
+
234 }
+
235
+
236 // Single trade
+
237 auto const amountActual = [&]() {
+
238 if (isDeposit == IsDeposit::Yes)
+
239 return ammAssetIn(
+
240 amountBalance, lptAMMBalance, lpTokensActual, tfee);
+
241 else if (!ammRoundingEnabled)
+
242 return ammAssetOut(
+
243 amountBalance, lptAMMBalance, lpTokens, tfee);
+
244 else
+
245 return ammAssetOut(
+
246 amountBalance, lptAMMBalance, lpTokensActual, tfee);
+
247 }();
+
248 if (!ammRoundingEnabled)
+
249 return amountActual < amount
+
250 ? std::make_tuple(amountActual, std::nullopt, lpTokensActual)
+
251 : std::make_tuple(amount, std::nullopt, lpTokensActual);
+
252 else
+
253 return std::make_tuple(amountActual, std::nullopt, lpTokensActual);
+
254 }
+
255
+
256 XRPL_ASSERT(
+
257 lpTokensActual == lpTokens,
+
258 "ripple::adjustAmountsByLPTokens : LP tokens match actual");
+
259
+
260 return {amount, amount2, lpTokensActual};
+
261}
+
262
+
263Number
+
264solveQuadraticEq(Number const& a, Number const& b, Number const& c)
+
265{
+
266 return (-b + root2(b * b - 4 * a * c)) / (2 * a);
+
267}
+
268
+
269// Minimize takerGets or takerPays
+
270std::optional<Number>
+
271solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c)
+
272{
+
273 auto const d = b * b - 4 * a * c;
+
274 if (d < 0)
+
275 return std::nullopt;
+
276 // use numerically stable citardauq formula for quadratic equation solution
+
277 // https://people.csail.mit.edu/bkph/articles/Quadratics.pdf
+
278 if (b > 0)
+
279 return (2 * c) / (-b - root2(d));
+
280 else
+
281 return (2 * c) / (-b + root2(d));
+
282}
+
283
+
284STAmount
+
285multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm)
+
286{
+
287 NumberRoundModeGuard g(rm);
+
288 auto const t = amount * frac;
+
289 return toSTAmount(amount.issue(), t, rm);
+
290}
+
291
+
292STAmount
+
293getRoundedAsset(
+
294 Rules const& rules,
+
295 std::function<Number()>&& noRoundCb,
+
296 STAmount const& balance,
+
297 std::function<Number()>&& productCb,
+
298 IsDeposit isDeposit)
+
299{
+
300 if (!rules.enabled(fixAMMv1_3))
+
301 return toSTAmount(balance.issue(), noRoundCb());
+
302
+
303 auto const rm = detail::getAssetRounding(isDeposit);
+
304 if (isDeposit == IsDeposit::Yes)
+
305 return multiply(balance, productCb(), rm);
+
306 NumberRoundModeGuard g(rm);
+
307 return toSTAmount(balance.issue(), productCb(), rm);
+
308}
+
309
+
310STAmount
+
311getRoundedLPTokens(
+
312 Rules const& rules,
+
313 STAmount const& balance,
+
314 Number const& frac,
+
315 IsDeposit isDeposit)
+
316{
+
317 if (!rules.enabled(fixAMMv1_3))
+
318 return toSTAmount(balance.issue(), balance * frac);
+
319
+
320 auto const rm = detail::getLPTokenRounding(isDeposit);
+
321 auto const tokens = multiply(balance, frac, rm);
+
322 return adjustLPTokens(balance, tokens, isDeposit);
+
323}
+
324
+
325STAmount
+
326getRoundedLPTokens(
+
327 Rules const& rules,
+
328 std::function<Number()>&& noRoundCb,
+
329 STAmount const& lptAMMBalance,
+
330 std::function<Number()>&& productCb,
+
331 IsDeposit isDeposit)
+
332{
+
333 if (!rules.enabled(fixAMMv1_3))
+
334 return toSTAmount(lptAMMBalance.issue(), noRoundCb());
+
335
+
336 auto const tokens = [&] {
+
337 auto const rm = detail::getLPTokenRounding(isDeposit);
+
338 if (isDeposit == IsDeposit::Yes)
+
339 {
+
340 NumberRoundModeGuard g(rm);
+
341 return toSTAmount(lptAMMBalance.issue(), productCb(), rm);
+
342 }
+
343 return multiply(lptAMMBalance, productCb(), rm);
+
344 }();
+
345 return adjustLPTokens(lptAMMBalance, tokens, isDeposit);
+
346}
+
347
+
348std::pair<STAmount, STAmount>
+
349adjustAssetInByTokens(
+
350 Rules const& rules,
+
351 STAmount const& balance,
+
352 STAmount const& amount,
+
353 STAmount const& lptAMMBalance,
+
354 STAmount const& tokens,
+
355 std::uint16_t tfee)
+
356{
+
357 if (!rules.enabled(fixAMMv1_3))
+
358 return {tokens, amount};
+
359 auto assetAdj = ammAssetIn(balance, lptAMMBalance, tokens, tfee);
+
360 auto tokensAdj = tokens;
+
361 // Rounding didn't work the right way.
+
362 // Try to adjust the original deposit amount by difference
+
363 // in adjust and original amount. Then adjust tokens and deposit amount.
+
364 if (assetAdj > amount)
+
365 {
+
366 auto const adjAmount = amount - (assetAdj - amount);
+
367 auto const t = lpTokensOut(balance, adjAmount, lptAMMBalance, tfee);
+
368 tokensAdj = adjustLPTokens(lptAMMBalance, t, IsDeposit::Yes);
+
369 assetAdj = ammAssetIn(balance, lptAMMBalance, tokensAdj, tfee);
+
370 }
+
371 return {tokensAdj, std::min(amount, assetAdj)};
+
372}
+
373
+
374std::pair<STAmount, STAmount>
+
375adjustAssetOutByTokens(
+
376 Rules const& rules,
+
377 STAmount const& balance,
+
378 STAmount const& amount,
+
379 STAmount const& lptAMMBalance,
+
380 STAmount const& tokens,
+
381 std::uint16_t tfee)
+
382{
+
383 if (!rules.enabled(fixAMMv1_3))
+
384 return {tokens, amount};
+
385 auto assetAdj = ammAssetOut(balance, lptAMMBalance, tokens, tfee);
+
386 auto tokensAdj = tokens;
+
387 // Rounding didn't work the right way.
+
388 // Try to adjust the original deposit amount by difference
+
389 // in adjust and original amount. Then adjust tokens and deposit amount.
+
390 if (assetAdj > amount)
+
391 {
+
392 auto const adjAmount = amount - (assetAdj - amount);
+
393 auto const t = lpTokensIn(balance, adjAmount, lptAMMBalance, tfee);
+
394 tokensAdj = adjustLPTokens(lptAMMBalance, t, IsDeposit::No);
+
395 assetAdj = ammAssetOut(balance, lptAMMBalance, tokensAdj, tfee);
+
396 }
+
397 return {tokensAdj, std::min(amount, assetAdj)};
+
398}
+
399
+
400Number
+
401adjustFracByTokens(
+
402 Rules const& rules,
+
403 STAmount const& lptAMMBalance,
+
404 STAmount const& tokens,
+
405 Number const& frac)
+
406{
+
407 if (!rules.enabled(fixAMMv1_3))
+
408 return frac;
+
409 return tokens / lptAMMBalance;
+
410}
+
411
+
412} // namespace ripple
A currency issued by an account.
Definition: Issue.h:36
+
Definition: Number.h:393
Definition: Number.h:36
+
rounding_mode
Definition: Number.h:178
@ downward
Definition: Number.h:178
+
@ upward
Definition: Number.h:178
+
static rounding_mode getround()
Definition: Number.cpp:47
static rounding_mode setround(rounding_mode mode)
Definition: Number.cpp:53
+
Rules controlling protocol behavior.
Definition: Rules.h:38
+
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
Definition: STAmount.h:50
Issue const & issue() const
Definition: STAmount.h:496
Definition: Number.h:371
+
T make_optional(T... args)
T make_tuple(T... args)
+
T min(T... args)
+
Number::rounding_mode getAssetRounding(IsDeposit isDeposit)
Definition: AMMHelpers.h:662
+
Number::rounding_mode getLPTokenRounding(IsDeposit isDeposit)
Definition: AMMHelpers.h:654
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
-
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
Definition: AMMHelpers.cpp:41
-
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, bool isDeposit)
Adjust LP tokens to deposit/withdraw.
Definition: AMMHelpers.cpp:133
-
std::optional< Number > solveQuadraticEqSmallest(Number const &a, Number const &b, Number const &c)
Solve quadratic equation to find takerGets or takerPays.
Definition: AMMHelpers.cpp:227
-
Number solveQuadraticEq(Number const &a, Number const &b, Number const &c)
Positive solution for quadratic equation: x = (-b + sqrt(b**2 + 4*a*c))/(2*a)
Definition: AMMHelpers.cpp:220
+
bool isFeatureEnabled(uint256 const &feature)
Definition: Rules.cpp:166
+
std::pair< STAmount, STAmount > adjustAssetInByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
Definition: AMMHelpers.cpp:349
+
std::optional< Number > solveQuadraticEqSmallest(Number const &a, Number const &b, Number const &c)
Solve quadratic equation to find takerGets or takerPays.
Definition: AMMHelpers.cpp:271
+
Number solveQuadraticEq(Number const &a, Number const &b, Number const &c)
Positive solution for quadratic equation: x = (-b + sqrt(b**2 + 4*a*c))/(2*a)
Definition: AMMHelpers.cpp:264
+
std::pair< STAmount, STAmount > adjustAssetOutByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
Definition: AMMHelpers.cpp:375
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
-
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
Definition: AMMHelpers.cpp:90
-
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
Definition: AMMHelpers.cpp:67
-
Number square(Number const &n)
Return square of n.
Definition: AMMHelpers.cpp:127
-
std::tuple< STAmount, std::optional< STAmount >, STAmount > adjustAmountsByLPTokens(STAmount const &amountBalance, STAmount const &amount, std::optional< STAmount > const &amount2, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee, bool isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
Definition: AMMHelpers.cpp:147
+
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
Definition: AMMHelpers.cpp:80
+
Number square(Number const &n)
Return square of n.
Definition: AMMHelpers.cpp:167
+
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:53
+
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
Definition: AMMHelpers.cpp:311
+
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
Definition: AMMHelpers.cpp:173
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
Definition: AMMCore.h:110
+
std::tuple< STAmount, std::optional< STAmount >, STAmount > adjustAmountsByLPTokens(STAmount const &amountBalance, STAmount const &amount, std::optional< STAmount > const &amount2, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee, IsDeposit isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
Definition: AMMHelpers.cpp:187
+
STAmount ammAssetOut(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
Definition: AMMHelpers.cpp:145
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition: AMMCore.h:101
+
IsDeposit
Definition: AMMHelpers.h:51
+
@ Yes
+
@ No
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition: AMMHelpers.cpp:25
std::optional< Rules > const & getCurrentTransactionRules()
Definition: Rules.cpp:47
+
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
Definition: AMMHelpers.cpp:112
Number feeMultHalf(std::uint16_t tfee)
Get fee multiplier (1 - tfee / 2) @tfee trading fee in basis points.
Definition: AMMCore.h:119
-
STAmount withdrawByTokens(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
Definition: AMMHelpers.cpp:114
+
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
Definition: AMMHelpers.cpp:45
+
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition: AMMHelpers.h:678
Number root2(Number f)
Definition: Number.cpp:701
+
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
Definition: AMMHelpers.cpp:401
+
diff --git a/AMMHelpers_8h_source.html b/AMMHelpers_8h_source.html index 606a736ad5..81cf78ec49 100644 --- a/AMMHelpers_8h_source.html +++ b/AMMHelpers_8h_source.html @@ -126,477 +126,596 @@ $(function() {
48
49} // namespace detail
50
-
56STAmount
-
57ammLPTokens(
-
58 STAmount const& asset1,
-
59 STAmount const& asset2,
-
60 Issue const& lptIssue);
-
61
-
69STAmount
-
70lpTokensIn(
-
71 STAmount const& asset1Balance,
-
72 STAmount const& asset1Deposit,
-
73 STAmount const& lptAMMBalance,
-
74 std::uint16_t tfee);
-
75
-
83STAmount
-
84ammAssetIn(
-
85 STAmount const& asset1Balance,
-
86 STAmount const& lptAMMBalance,
-
87 STAmount const& lpTokens,
-
88 std::uint16_t tfee);
-
89
-
98STAmount
-
99lpTokensOut(
-
100 STAmount const& asset1Balance,
-
101 STAmount const& asset1Withdraw,
-
102 STAmount const& lptAMMBalance,
-
103 std::uint16_t tfee);
-
104
-
112STAmount
-
113withdrawByTokens(
-
114 STAmount const& assetBalance,
-
115 STAmount const& lptAMMBalance,
-
116 STAmount const& lpTokens,
-
117 std::uint16_t tfee);
-
118
-
126inline bool
-
127withinRelativeDistance(
-
128 Quality const& calcQuality,
-
129 Quality const& reqQuality,
-
130 Number const& dist)
-
131{
-
132 if (calcQuality == reqQuality)
-
133 return true;
-
134 auto const [min, max] = std::minmax(calcQuality, reqQuality);
-
135 // Relative distance is (max - min)/max. Can't use basic operations
-
136 // on Quality. Have to use Quality::rate() instead, which
-
137 // is inverse of quality: (1/max.rate - 1/min.rate)/(1/max.rate)
-
138 return ((min.rate() - max.rate()) / min.rate()) < dist;
-
139}
-
140
-
148// clang-format off
-
149template <typename Amt>
-
150 requires(
-
151 std::is_same_v<Amt, STAmount> || std::is_same_v<Amt, IOUAmount> ||
-
152 std::is_same_v<Amt, XRPAmount> || std::is_same_v<Amt, Number>)
-
153bool
-
154withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist)
-
155{
-
156 if (calc == req)
-
157 return true;
-
158 auto const [min, max] = std::minmax(calc, req);
-
159 return ((max - min) / max) < dist;
-
160}
-
161// clang-format on
-
162
-
166std::optional<Number>
-
167solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c);
-
168
-
192template <typename TIn, typename TOut>
-
193std::optional<TAmounts<TIn, TOut>>
-
194getAMMOfferStartWithTakerGets(
-
195 TAmounts<TIn, TOut> const& pool,
-
196 Quality const& targetQuality,
-
197 std::uint16_t const& tfee)
-
198{
-
199 if (targetQuality.rate() == beast::zero)
-
200 return std::nullopt;
-
201
-
202 NumberRoundModeGuard mg(Number::to_nearest);
-
203 auto const f = feeMult(tfee);
-
204 auto const a = 1;
-
205 auto const b = pool.in * (1 - 1 / f) / targetQuality.rate() - 2 * pool.out;
-
206 auto const c =
-
207 pool.out * pool.out - (pool.in * pool.out) / targetQuality.rate();
-
208
-
209 auto nTakerGets = solveQuadraticEqSmallest(a, b, c);
-
210 if (!nTakerGets || *nTakerGets <= 0)
-
211 return std::nullopt; // LCOV_EXCL_LINE
-
212
-
213 auto const nTakerGetsConstraint =
-
214 pool.out - pool.in / (targetQuality.rate() * f);
-
215 if (nTakerGetsConstraint <= 0)
-
216 return std::nullopt;
-
217
-
218 // Select the smallest to maximize the quality
-
219 if (nTakerGetsConstraint < *nTakerGets)
-
220 nTakerGets = nTakerGetsConstraint;
-
221
-
222 auto getAmounts = [&pool, &tfee](Number const& nTakerGetsProposed) {
-
223 // Round downward to minimize the offer and to maximize the quality.
-
224 // This has the most impact when takerGets is XRP.
-
225 auto const takerGets = toAmount<TOut>(
-
226 getIssue(pool.out), nTakerGetsProposed, Number::downward);
-
227 return TAmounts<TIn, TOut>{
-
228 swapAssetOut(pool, takerGets, tfee), takerGets};
-
229 };
-
230
-
231 // Try to reduce the offer size to improve the quality.
-
232 // The quality might still not match the targetQuality for a tiny offer.
-
233 if (auto const amounts = getAmounts(*nTakerGets);
-
234 Quality{amounts} < targetQuality)
-
235 return getAmounts(detail::reduceOffer(amounts.out));
-
236 else
-
237 return amounts;
-
238}
-
239
-
263template <typename TIn, typename TOut>
-
264std::optional<TAmounts<TIn, TOut>>
-
265getAMMOfferStartWithTakerPays(
-
266 TAmounts<TIn, TOut> const& pool,
-
267 Quality const& targetQuality,
-
268 std::uint16_t tfee)
-
269{
-
270 if (targetQuality.rate() == beast::zero)
-
271 return std::nullopt;
-
272
-
273 NumberRoundModeGuard mg(Number::to_nearest);
-
274 auto const f = feeMult(tfee);
-
275 auto const& a = f;
-
276 auto const b = pool.in * (1 + f);
-
277 auto const c =
-
278 pool.in * pool.in - pool.in * pool.out * targetQuality.rate();
-
279
-
280 auto nTakerPays = solveQuadraticEqSmallest(a, b, c);
-
281 if (!nTakerPays || nTakerPays <= 0)
-
282 return std::nullopt; // LCOV_EXCL_LINE
-
283
-
284 auto const nTakerPaysConstraint =
-
285 pool.out * targetQuality.rate() - pool.in / f;
-
286 if (nTakerPaysConstraint <= 0)
-
287 return std::nullopt;
-
288
-
289 // Select the smallest to maximize the quality
-
290 if (nTakerPaysConstraint < *nTakerPays)
-
291 nTakerPays = nTakerPaysConstraint;
-
292
-
293 auto getAmounts = [&pool, &tfee](Number const& nTakerPaysProposed) {
-
294 // Round downward to minimize the offer and to maximize the quality.
-
295 // This has the most impact when takerPays is XRP.
-
296 auto const takerPays = toAmount<TIn>(
-
297 getIssue(pool.in), nTakerPaysProposed, Number::downward);
-
298 return TAmounts<TIn, TOut>{
-
299 takerPays, swapAssetIn(pool, takerPays, tfee)};
-
300 };
-
301
-
302 // Try to reduce the offer size to improve the quality.
-
303 // The quality might still not match the targetQuality for a tiny offer.
-
304 if (auto const amounts = getAmounts(*nTakerPays);
-
305 Quality{amounts} < targetQuality)
-
306 return getAmounts(detail::reduceOffer(amounts.in));
-
307 else
-
308 return amounts;
-
309}
-
310
-
327template <typename TIn, typename TOut>
-
328std::optional<TAmounts<TIn, TOut>>
-
329changeSpotPriceQuality(
-
330 TAmounts<TIn, TOut> const& pool,
-
331 Quality const& quality,
-
332 std::uint16_t tfee,
-
333 Rules const& rules,
-
334 beast::Journal j)
-
335{
-
336 if (!rules.enabled(fixAMMv1_1))
-
337 {
-
338 // Finds takerPays (i) and takerGets (o) such that given pool
-
339 // composition poolGets(I) and poolPays(O): (O - o) / (I + i) = quality.
-
340 // Where takerGets is calculated as the swapAssetIn (see below).
-
341 // The above equation produces the quadratic equation:
-
342 // i^2*(1-fee) + i*I*(2-fee) + I^2 - I*O/quality,
-
343 // which is solved for i, and o is found with swapAssetIn().
-
344 auto const f = feeMult(tfee); // 1 - fee
-
345 auto const& a = f;
-
346 auto const b = pool.in * (1 + f);
-
347 Number const c =
-
348 pool.in * pool.in - pool.in * pool.out * quality.rate();
-
349 if (auto const res = b * b - 4 * a * c; res < 0)
-
350 return std::nullopt; // LCOV_EXCL_LINE
-
351 else if (auto const nTakerPaysPropose = (-b + root2(res)) / (2 * a);
-
352 nTakerPaysPropose > 0)
-
353 {
-
354 auto const nTakerPays = [&]() {
-
355 // The fee might make the AMM offer quality less than CLOB
-
356 // quality. Therefore, AMM offer has to satisfy this constraint:
-
357 // o / i >= q. Substituting o with swapAssetIn() gives: i <= O /
-
358 // q - I / (1 - fee).
-
359 auto const nTakerPaysConstraint =
-
360 pool.out * quality.rate() - pool.in / f;
-
361 if (nTakerPaysPropose > nTakerPaysConstraint)
-
362 return nTakerPaysConstraint;
-
363 return nTakerPaysPropose;
-
364 }();
-
365 if (nTakerPays <= 0)
-
366 {
-
367 JLOG(j.trace())
-
368 << "changeSpotPriceQuality calc failed: "
-
369 << to_string(pool.in) << " " << to_string(pool.out) << " "
-
370 << quality << " " << tfee;
-
371 return std::nullopt;
-
372 }
-
373 auto const takerPays =
-
374 toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
-
375 // should not fail
-
376 if (auto const amounts =
-
377 TAmounts<TIn, TOut>{
-
378 takerPays, swapAssetIn(pool, takerPays, tfee)};
-
379 Quality{amounts} < quality &&
-
380 !withinRelativeDistance(
-
381 Quality{amounts}, quality, Number(1, -7)))
-
382 {
-
383 JLOG(j.error())
-
384 << "changeSpotPriceQuality failed: " << to_string(pool.in)
-
385 << " " << to_string(pool.out) << " "
-
386 << " " << quality << " " << tfee << " "
-
387 << to_string(amounts.in) << " " << to_string(amounts.out);
-
388 Throw<std::runtime_error>("changeSpotPriceQuality failed");
-
389 }
-
390 else
-
391 {
-
392 JLOG(j.trace())
-
393 << "changeSpotPriceQuality succeeded: "
-
394 << to_string(pool.in) << " " << to_string(pool.out) << " "
-
395 << " " << quality << " " << tfee << " "
-
396 << to_string(amounts.in) << " " << to_string(amounts.out);
-
397 return amounts;
-
398 }
-
399 }
-
400 JLOG(j.trace()) << "changeSpotPriceQuality calc failed: "
-
401 << to_string(pool.in) << " " << to_string(pool.out)
-
402 << " " << quality << " " << tfee;
-
403 return std::nullopt;
-
404 }
-
405
-
406 // Generate the offer starting with XRP side. Return seated offer amounts
-
407 // if the offer can be generated, otherwise nullopt.
-
408 auto const amounts = [&]() {
-
409 if (isXRP(getIssue(pool.out)))
-
410 return getAMMOfferStartWithTakerGets(pool, quality, tfee);
-
411 return getAMMOfferStartWithTakerPays(pool, quality, tfee);
-
412 }();
-
413 if (!amounts)
-
414 {
-
415 JLOG(j.trace()) << "changeSpotPrice calc failed: " << to_string(pool.in)
-
416 << " " << to_string(pool.out) << " " << quality << " "
-
417 << tfee << std::endl;
-
418 return std::nullopt;
-
419 }
-
420
-
421 if (Quality{*amounts} < quality)
-
422 {
-
423 JLOG(j.error()) << "changeSpotPriceQuality failed: "
-
424 << to_string(pool.in) << " " << to_string(pool.out)
-
425 << " " << quality << " " << tfee << " "
-
426 << to_string(amounts->in) << " "
-
427 << to_string(amounts->out);
-
428 return std::nullopt;
-
429 }
-
430
-
431 JLOG(j.trace()) << "changeSpotPriceQuality succeeded: "
-
432 << to_string(pool.in) << " " << to_string(pool.out) << " "
-
433 << " " << quality << " " << tfee << " "
-
434 << to_string(amounts->in) << " " << to_string(amounts->out);
-
435
-
436 return amounts;
-
437}
-
438
-
460template <typename TIn, typename TOut>
-
461TOut
-
462swapAssetIn(
-
463 TAmounts<TIn, TOut> const& pool,
-
464 TIn const& assetIn,
-
465 std::uint16_t tfee)
-
466{
-
467 if (auto const& rules = getCurrentTransactionRules();
-
468 rules && rules->enabled(fixAMMv1_1))
-
469 {
-
470 // set rounding to always favor the amm. Clip to zero.
-
471 // calculate:
-
472 // pool.out -
-
473 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
-
474 // and explicitly set the rounding modes
-
475 // Favoring the amm means we should:
-
476 // minimize:
-
477 // pool.out -
-
478 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
-
479 // maximize:
+
51enum class IsDeposit : bool { No = false, Yes = true };
+
52
+
58STAmount
+
59ammLPTokens(
+
60 STAmount const& asset1,
+
61 STAmount const& asset2,
+
62 Issue const& lptIssue);
+
63
+
71STAmount
+
72lpTokensOut(
+
73 STAmount const& asset1Balance,
+
74 STAmount const& asset1Deposit,
+
75 STAmount const& lptAMMBalance,
+
76 std::uint16_t tfee);
+
77
+
85STAmount
+
86ammAssetIn(
+
87 STAmount const& asset1Balance,
+
88 STAmount const& lptAMMBalance,
+
89 STAmount const& lpTokens,
+
90 std::uint16_t tfee);
+
91
+
100STAmount
+
101lpTokensIn(
+
102 STAmount const& asset1Balance,
+
103 STAmount const& asset1Withdraw,
+
104 STAmount const& lptAMMBalance,
+
105 std::uint16_t tfee);
+
106
+
114STAmount
+
115ammAssetOut(
+
116 STAmount const& assetBalance,
+
117 STAmount const& lptAMMBalance,
+
118 STAmount const& lpTokens,
+
119 std::uint16_t tfee);
+
120
+
128inline bool
+
129withinRelativeDistance(
+
130 Quality const& calcQuality,
+
131 Quality const& reqQuality,
+
132 Number const& dist)
+
133{
+
134 if (calcQuality == reqQuality)
+
135 return true;
+
136 auto const [min, max] = std::minmax(calcQuality, reqQuality);
+
137 // Relative distance is (max - min)/max. Can't use basic operations
+
138 // on Quality. Have to use Quality::rate() instead, which
+
139 // is inverse of quality: (1/max.rate - 1/min.rate)/(1/max.rate)
+
140 return ((min.rate() - max.rate()) / min.rate()) < dist;
+
141}
+
142
+
150// clang-format off
+
151template <typename Amt>
+
152 requires(
+
153 std::is_same_v<Amt, STAmount> || std::is_same_v<Amt, IOUAmount> ||
+
154 std::is_same_v<Amt, XRPAmount> || std::is_same_v<Amt, Number>)
+
155bool
+
156withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist)
+
157{
+
158 if (calc == req)
+
159 return true;
+
160 auto const [min, max] = std::minmax(calc, req);
+
161 return ((max - min) / max) < dist;
+
162}
+
163// clang-format on
+
164
+
168std::optional<Number>
+
169solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c);
+
170
+
194template <typename TIn, typename TOut>
+
195std::optional<TAmounts<TIn, TOut>>
+
196getAMMOfferStartWithTakerGets(
+
197 TAmounts<TIn, TOut> const& pool,
+
198 Quality const& targetQuality,
+
199 std::uint16_t const& tfee)
+
200{
+
201 if (targetQuality.rate() == beast::zero)
+
202 return std::nullopt;
+
203
+
204 NumberRoundModeGuard mg(Number::to_nearest);
+
205 auto const f = feeMult(tfee);
+
206 auto const a = 1;
+
207 auto const b = pool.in * (1 - 1 / f) / targetQuality.rate() - 2 * pool.out;
+
208 auto const c =
+
209 pool.out * pool.out - (pool.in * pool.out) / targetQuality.rate();
+
210
+
211 auto nTakerGets = solveQuadraticEqSmallest(a, b, c);
+
212 if (!nTakerGets || *nTakerGets <= 0)
+
213 return std::nullopt; // LCOV_EXCL_LINE
+
214
+
215 auto const nTakerGetsConstraint =
+
216 pool.out - pool.in / (targetQuality.rate() * f);
+
217 if (nTakerGetsConstraint <= 0)
+
218 return std::nullopt;
+
219
+
220 // Select the smallest to maximize the quality
+
221 if (nTakerGetsConstraint < *nTakerGets)
+
222 nTakerGets = nTakerGetsConstraint;
+
223
+
224 auto getAmounts = [&pool, &tfee](Number const& nTakerGetsProposed) {
+
225 // Round downward to minimize the offer and to maximize the quality.
+
226 // This has the most impact when takerGets is XRP.
+
227 auto const takerGets = toAmount<TOut>(
+
228 getIssue(pool.out), nTakerGetsProposed, Number::downward);
+
229 return TAmounts<TIn, TOut>{
+
230 swapAssetOut(pool, takerGets, tfee), takerGets};
+
231 };
+
232
+
233 // Try to reduce the offer size to improve the quality.
+
234 // The quality might still not match the targetQuality for a tiny offer.
+
235 if (auto const amounts = getAmounts(*nTakerGets);
+
236 Quality{amounts} < targetQuality)
+
237 return getAmounts(detail::reduceOffer(amounts.out));
+
238 else
+
239 return amounts;
+
240}
+
241
+
265template <typename TIn, typename TOut>
+
266std::optional<TAmounts<TIn, TOut>>
+
267getAMMOfferStartWithTakerPays(
+
268 TAmounts<TIn, TOut> const& pool,
+
269 Quality const& targetQuality,
+
270 std::uint16_t tfee)
+
271{
+
272 if (targetQuality.rate() == beast::zero)
+
273 return std::nullopt;
+
274
+
275 NumberRoundModeGuard mg(Number::to_nearest);
+
276 auto const f = feeMult(tfee);
+
277 auto const& a = f;
+
278 auto const b = pool.in * (1 + f);
+
279 auto const c =
+
280 pool.in * pool.in - pool.in * pool.out * targetQuality.rate();
+
281
+
282 auto nTakerPays = solveQuadraticEqSmallest(a, b, c);
+
283 if (!nTakerPays || nTakerPays <= 0)
+
284 return std::nullopt; // LCOV_EXCL_LINE
+
285
+
286 auto const nTakerPaysConstraint =
+
287 pool.out * targetQuality.rate() - pool.in / f;
+
288 if (nTakerPaysConstraint <= 0)
+
289 return std::nullopt;
+
290
+
291 // Select the smallest to maximize the quality
+
292 if (nTakerPaysConstraint < *nTakerPays)
+
293 nTakerPays = nTakerPaysConstraint;
+
294
+
295 auto getAmounts = [&pool, &tfee](Number const& nTakerPaysProposed) {
+
296 // Round downward to minimize the offer and to maximize the quality.
+
297 // This has the most impact when takerPays is XRP.
+
298 auto const takerPays = toAmount<TIn>(
+
299 getIssue(pool.in), nTakerPaysProposed, Number::downward);
+
300 return TAmounts<TIn, TOut>{
+
301 takerPays, swapAssetIn(pool, takerPays, tfee)};
+
302 };
+
303
+
304 // Try to reduce the offer size to improve the quality.
+
305 // The quality might still not match the targetQuality for a tiny offer.
+
306 if (auto const amounts = getAmounts(*nTakerPays);
+
307 Quality{amounts} < targetQuality)
+
308 return getAmounts(detail::reduceOffer(amounts.in));
+
309 else
+
310 return amounts;
+
311}
+
312
+
329template <typename TIn, typename TOut>
+
330std::optional<TAmounts<TIn, TOut>>
+
331changeSpotPriceQuality(
+
332 TAmounts<TIn, TOut> const& pool,
+
333 Quality const& quality,
+
334 std::uint16_t tfee,
+
335 Rules const& rules,
+
336 beast::Journal j)
+
337{
+
338 if (!rules.enabled(fixAMMv1_1))
+
339 {
+
340 // Finds takerPays (i) and takerGets (o) such that given pool
+
341 // composition poolGets(I) and poolPays(O): (O - o) / (I + i) = quality.
+
342 // Where takerGets is calculated as the swapAssetIn (see below).
+
343 // The above equation produces the quadratic equation:
+
344 // i^2*(1-fee) + i*I*(2-fee) + I^2 - I*O/quality,
+
345 // which is solved for i, and o is found with swapAssetIn().
+
346 auto const f = feeMult(tfee); // 1 - fee
+
347 auto const& a = f;
+
348 auto const b = pool.in * (1 + f);
+
349 Number const c =
+
350 pool.in * pool.in - pool.in * pool.out * quality.rate();
+
351 if (auto const res = b * b - 4 * a * c; res < 0)
+
352 return std::nullopt; // LCOV_EXCL_LINE
+
353 else if (auto const nTakerPaysPropose = (-b + root2(res)) / (2 * a);
+
354 nTakerPaysPropose > 0)
+
355 {
+
356 auto const nTakerPays = [&]() {
+
357 // The fee might make the AMM offer quality less than CLOB
+
358 // quality. Therefore, AMM offer has to satisfy this constraint:
+
359 // o / i >= q. Substituting o with swapAssetIn() gives: i <= O /
+
360 // q - I / (1 - fee).
+
361 auto const nTakerPaysConstraint =
+
362 pool.out * quality.rate() - pool.in / f;
+
363 if (nTakerPaysPropose > nTakerPaysConstraint)
+
364 return nTakerPaysConstraint;
+
365 return nTakerPaysPropose;
+
366 }();
+
367 if (nTakerPays <= 0)
+
368 {
+
369 JLOG(j.trace())
+
370 << "changeSpotPriceQuality calc failed: "
+
371 << to_string(pool.in) << " " << to_string(pool.out) << " "
+
372 << quality << " " << tfee;
+
373 return std::nullopt;
+
374 }
+
375 auto const takerPays =
+
376 toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
+
377 // should not fail
+
378 if (auto const amounts =
+
379 TAmounts<TIn, TOut>{
+
380 takerPays, swapAssetIn(pool, takerPays, tfee)};
+
381 Quality{amounts} < quality &&
+
382 !withinRelativeDistance(
+
383 Quality{amounts}, quality, Number(1, -7)))
+
384 {
+
385 JLOG(j.error())
+
386 << "changeSpotPriceQuality failed: " << to_string(pool.in)
+
387 << " " << to_string(pool.out) << " "
+
388 << " " << quality << " " << tfee << " "
+
389 << to_string(amounts.in) << " " << to_string(amounts.out);
+
390 Throw<std::runtime_error>("changeSpotPriceQuality failed");
+
391 }
+
392 else
+
393 {
+
394 JLOG(j.trace())
+
395 << "changeSpotPriceQuality succeeded: "
+
396 << to_string(pool.in) << " " << to_string(pool.out) << " "
+
397 << " " << quality << " " << tfee << " "
+
398 << to_string(amounts.in) << " " << to_string(amounts.out);
+
399 return amounts;
+
400 }
+
401 }
+
402 JLOG(j.trace()) << "changeSpotPriceQuality calc failed: "
+
403 << to_string(pool.in) << " " << to_string(pool.out)
+
404 << " " << quality << " " << tfee;
+
405 return std::nullopt;
+
406 }
+
407
+
408 // Generate the offer starting with XRP side. Return seated offer amounts
+
409 // if the offer can be generated, otherwise nullopt.
+
410 auto const amounts = [&]() {
+
411 if (isXRP(getIssue(pool.out)))
+
412 return getAMMOfferStartWithTakerGets(pool, quality, tfee);
+
413 return getAMMOfferStartWithTakerPays(pool, quality, tfee);
+
414 }();
+
415 if (!amounts)
+
416 {
+
417 JLOG(j.trace()) << "changeSpotPrice calc failed: " << to_string(pool.in)
+
418 << " " << to_string(pool.out) << " " << quality << " "
+
419 << tfee << std::endl;
+
420 return std::nullopt;
+
421 }
+
422
+
423 if (Quality{*amounts} < quality)
+
424 {
+
425 JLOG(j.error()) << "changeSpotPriceQuality failed: "
+
426 << to_string(pool.in) << " " << to_string(pool.out)
+
427 << " " << quality << " " << tfee << " "
+
428 << to_string(amounts->in) << " "
+
429 << to_string(amounts->out);
+
430 return std::nullopt;
+
431 }
+
432
+
433 JLOG(j.trace()) << "changeSpotPriceQuality succeeded: "
+
434 << to_string(pool.in) << " " << to_string(pool.out) << " "
+
435 << " " << quality << " " << tfee << " "
+
436 << to_string(amounts->in) << " " << to_string(amounts->out);
+
437
+
438 return amounts;
+
439}
+
440
+
462template <typename TIn, typename TOut>
+
463TOut
+
464swapAssetIn(
+
465 TAmounts<TIn, TOut> const& pool,
+
466 TIn const& assetIn,
+
467 std::uint16_t tfee)
+
468{
+
469 if (auto const& rules = getCurrentTransactionRules();
+
470 rules && rules->enabled(fixAMMv1_1))
+
471 {
+
472 // set rounding to always favor the amm. Clip to zero.
+
473 // calculate:
+
474 // pool.out -
+
475 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
+
476 // and explicitly set the rounding modes
+
477 // Favoring the amm means we should:
+
478 // minimize:
+
479 // pool.out -
480 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
-
481 // (pool.in * pool.out)
-
482 // minimize:
-
483 // (pool.in + assetIn * feeMult(tfee)),
+
481 // maximize:
+
482 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
+
483 // (pool.in * pool.out)
484 // minimize:
-
485 // assetIn * feeMult(tfee)
-
486 // feeMult is: (1-fee), fee is tfee/100000
-
487 // minimize:
-
488 // 1-fee
-
489 // maximize:
-
490 // fee
-
491 saveNumberRoundMode _{Number::getround()};
-
492
-
493 Number::setround(Number::upward);
-
494 auto const numerator = pool.in * pool.out;
-
495 auto const fee = getFee(tfee);
-
496
-
497 Number::setround(Number::downward);
-
498 auto const denom = pool.in + assetIn * (1 - fee);
-
499
-
500 if (denom.signum() <= 0)
-
501 return toAmount<TOut>(getIssue(pool.out), 0);
-
502
-
503 Number::setround(Number::upward);
-
504 auto const ratio = numerator / denom;
-
505
-
506 Number::setround(Number::downward);
-
507 auto const swapOut = pool.out - ratio;
-
508
-
509 if (swapOut.signum() < 0)
-
510 return toAmount<TOut>(getIssue(pool.out), 0);
-
511
-
512 return toAmount<TOut>(getIssue(pool.out), swapOut, Number::downward);
-
513 }
-
514 else
-
515 {
-
516 return toAmount<TOut>(
-
517 getIssue(pool.out),
-
518 pool.out -
-
519 (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
-
520 Number::downward);
-
521 }
-
522}
-
523
-
533template <typename TIn, typename TOut>
-
534TIn
-
535swapAssetOut(
-
536 TAmounts<TIn, TOut> const& pool,
-
537 TOut const& assetOut,
-
538 std::uint16_t tfee)
-
539{
-
540 if (auto const& rules = getCurrentTransactionRules();
-
541 rules && rules->enabled(fixAMMv1_1))
-
542 {
-
543 // set rounding to always favor the amm. Clip to zero.
-
544 // calculate:
-
545 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
-
546 // (1-tfee/100000)
-
547 // maximize:
-
548 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in)
+
485 // (pool.in + assetIn * feeMult(tfee)),
+
486 // minimize:
+
487 // assetIn * feeMult(tfee)
+
488 // feeMult is: (1-fee), fee is tfee/100000
+
489 // minimize:
+
490 // 1-fee
+
491 // maximize:
+
492 // fee
+
493 saveNumberRoundMode _{Number::getround()};
+
494
+
495 Number::setround(Number::upward);
+
496 auto const numerator = pool.in * pool.out;
+
497 auto const fee = getFee(tfee);
+
498
+
499 Number::setround(Number::downward);
+
500 auto const denom = pool.in + assetIn * (1 - fee);
+
501
+
502 if (denom.signum() <= 0)
+
503 return toAmount<TOut>(getIssue(pool.out), 0);
+
504
+
505 Number::setround(Number::upward);
+
506 auto const ratio = numerator / denom;
+
507
+
508 Number::setround(Number::downward);
+
509 auto const swapOut = pool.out - ratio;
+
510
+
511 if (swapOut.signum() < 0)
+
512 return toAmount<TOut>(getIssue(pool.out), 0);
+
513
+
514 return toAmount<TOut>(getIssue(pool.out), swapOut, Number::downward);
+
515 }
+
516 else
+
517 {
+
518 return toAmount<TOut>(
+
519 getIssue(pool.out),
+
520 pool.out -
+
521 (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
+
522 Number::downward);
+
523 }
+
524}
+
525
+
535template <typename TIn, typename TOut>
+
536TIn
+
537swapAssetOut(
+
538 TAmounts<TIn, TOut> const& pool,
+
539 TOut const& assetOut,
+
540 std::uint16_t tfee)
+
541{
+
542 if (auto const& rules = getCurrentTransactionRules();
+
543 rules && rules->enabled(fixAMMv1_1))
+
544 {
+
545 // set rounding to always favor the amm. Clip to zero.
+
546 // calculate:
+
547 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
+
548 // (1-tfee/100000)
549 // maximize:
-
550 // (pool.in * pool.out) / (pool.out - assetOut)
+
550 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in)
551 // maximize:
-
552 // (pool.in * pool.out)
-
553 // minimize
-
554 // (pool.out - assetOut)
-
555 // minimize:
-
556 // (1-tfee/100000)
-
557 // maximize:
-
558 // tfee/100000
-
559
-
560 saveNumberRoundMode _{Number::getround()};
+
552 // (pool.in * pool.out) / (pool.out - assetOut)
+
553 // maximize:
+
554 // (pool.in * pool.out)
+
555 // minimize
+
556 // (pool.out - assetOut)
+
557 // minimize:
+
558 // (1-tfee/100000)
+
559 // maximize:
+
560 // tfee/100000
561
-
562 Number::setround(Number::upward);
-
563 auto const numerator = pool.in * pool.out;
-
564
-
565 Number::setround(Number::downward);
-
566 auto const denom = pool.out - assetOut;
-
567 if (denom.signum() <= 0)
-
568 {
-
569 return toMaxAmount<TIn>(getIssue(pool.in));
-
570 }
-
571
-
572 Number::setround(Number::upward);
-
573 auto const ratio = numerator / denom;
-
574 auto const numerator2 = ratio - pool.in;
-
575 auto const fee = getFee(tfee);
-
576
-
577 Number::setround(Number::downward);
-
578 auto const feeMult = 1 - fee;
-
579
-
580 Number::setround(Number::upward);
-
581 auto const swapIn = numerator2 / feeMult;
-
582 if (swapIn.signum() < 0)
-
583 return toAmount<TIn>(getIssue(pool.in), 0);
-
584
-
585 return toAmount<TIn>(getIssue(pool.in), swapIn, Number::upward);
-
586 }
-
587 else
-
588 {
-
589 return toAmount<TIn>(
-
590 getIssue(pool.in),
-
591 ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
-
592 feeMult(tfee),
-
593 Number::upward);
-
594 }
-
595}
-
596
-
599Number
-
600square(Number const& n);
-
601
-
613STAmount
-
614adjustLPTokens(
-
615 STAmount const& lptAMMBalance,
-
616 STAmount const& lpTokens,
-
617 bool isDeposit);
-
618
-
630std::tuple<STAmount, std::optional<STAmount>, STAmount>
-
631adjustAmountsByLPTokens(
-
632 STAmount const& amountBalance,
-
633 STAmount const& amount,
-
634 std::optional<STAmount> const& amount2,
-
635 STAmount const& lptAMMBalance,
-
636 STAmount const& lpTokens,
-
637 std::uint16_t tfee,
-
638 bool isDeposit);
-
639
-
643Number
-
644solveQuadraticEq(Number const& a, Number const& b, Number const& c);
-
645
-
646} // namespace ripple
+
562 saveNumberRoundMode _{Number::getround()};
+
563
+
564 Number::setround(Number::upward);
+
565 auto const numerator = pool.in * pool.out;
+
566
+
567 Number::setround(Number::downward);
+
568 auto const denom = pool.out - assetOut;
+
569 if (denom.signum() <= 0)
+
570 {
+
571 return toMaxAmount<TIn>(getIssue(pool.in));
+
572 }
+
573
+
574 Number::setround(Number::upward);
+
575 auto const ratio = numerator / denom;
+
576 auto const numerator2 = ratio - pool.in;
+
577 auto const fee = getFee(tfee);
+
578
+
579 Number::setround(Number::downward);
+
580 auto const feeMult = 1 - fee;
+
581
+
582 Number::setround(Number::upward);
+
583 auto const swapIn = numerator2 / feeMult;
+
584 if (swapIn.signum() < 0)
+
585 return toAmount<TIn>(getIssue(pool.in), 0);
+
586
+
587 return toAmount<TIn>(getIssue(pool.in), swapIn, Number::upward);
+
588 }
+
589 else
+
590 {
+
591 return toAmount<TIn>(
+
592 getIssue(pool.in),
+
593 ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
+
594 feeMult(tfee),
+
595 Number::upward);
+
596 }
+
597}
+
598
+
601Number
+
602square(Number const& n);
+
603
+
615STAmount
+
616adjustLPTokens(
+
617 STAmount const& lptAMMBalance,
+
618 STAmount const& lpTokens,
+
619 IsDeposit isDeposit);
+
620
+
632std::tuple<STAmount, std::optional<STAmount>, STAmount>
+
633adjustAmountsByLPTokens(
+
634 STAmount const& amountBalance,
+
635 STAmount const& amount,
+
636 std::optional<STAmount> const& amount2,
+
637 STAmount const& lptAMMBalance,
+
638 STAmount const& lpTokens,
+
639 std::uint16_t tfee,
+
640 IsDeposit isDeposit);
+
641
+
645Number
+
646solveQuadraticEq(Number const& a, Number const& b, Number const& c);
647
-
648#endif // RIPPLE_APP_MISC_AMMHELPERS_H_INCLUDED
+
648STAmount
+
649multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm);
+
650
+
651namespace detail {
+
652
+
653inline Number::rounding_mode
+
654getLPTokenRounding(IsDeposit isDeposit)
+
655{
+
656 // Minimize on deposit, maximize on withdraw to ensure
+
657 // AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
+
658 return isDeposit == IsDeposit::Yes ? Number::downward : Number::upward;
+
659}
+
660
+
661inline Number::rounding_mode
+
662getAssetRounding(IsDeposit isDeposit)
+
663{
+
664 // Maximize on deposit, minimize on withdraw to ensure
+
665 // AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
+
666 return isDeposit == IsDeposit::Yes ? Number::upward : Number::downward;
+
667}
+
668
+
669} // namespace detail
+
670
+
676template <typename A>
+
677STAmount
+
678getRoundedAsset(
+
679 Rules const& rules,
+
680 STAmount const& balance,
+
681 A const& frac,
+
682 IsDeposit isDeposit)
+
683{
+
684 if (!rules.enabled(fixAMMv1_3))
+
685 {
+
686 if constexpr (std::is_same_v<A, STAmount>)
+
687 return multiply(balance, frac, balance.issue());
+
688 else
+
689 return toSTAmount(balance.issue(), balance * frac);
+
690 }
+
691 auto const rm = detail::getAssetRounding(isDeposit);
+
692 return multiply(balance, frac, rm);
+
693}
+
694
+
704STAmount
+
705getRoundedAsset(
+
706 Rules const& rules,
+
707 std::function<Number()>&& noRoundCb,
+
708 STAmount const& balance,
+
709 std::function<Number()>&& productCb,
+
710 IsDeposit isDeposit);
+
711
+
719STAmount
+
720getRoundedLPTokens(
+
721 Rules const& rules,
+
722 STAmount const& balance,
+
723 Number const& frac,
+
724 IsDeposit isDeposit);
+
725
+
737STAmount
+
738getRoundedLPTokens(
+
739 Rules const& rules,
+
740 std::function<Number()>&& noRoundCb,
+
741 STAmount const& lptAMMBalance,
+
742 std::function<Number()>&& productCb,
+
743 IsDeposit isDeposit);
+
744
+
745/* Next two functions adjust asset in/out amount to factor in the adjusted
+
746 * lptokens. The lptokens are calculated from the asset in/out. The lptokens are
+
747 * then adjusted to factor in the loss in precision. The adjusted lptokens might
+
748 * be less than the initially calculated tokens. Therefore, the asset in/out
+
749 * must be adjusted. The rounding might result in the adjusted amount being
+
750 * greater than the original asset in/out amount. If this happens,
+
751 * then the original amount is reduced by the difference in the adjusted amount
+
752 * and the original amount. The actual tokens and the actual adjusted amount
+
753 * are then recalculated. The minimum of the original and the actual
+
754 * adjusted amount is returned.
+
755 */
+
756std::pair<STAmount, STAmount>
+
757adjustAssetInByTokens(
+
758 Rules const& rules,
+
759 STAmount const& balance,
+
760 STAmount const& amount,
+
761 STAmount const& lptAMMBalance,
+
762 STAmount const& tokens,
+
763 std::uint16_t tfee);
+
764std::pair<STAmount, STAmount>
+
765adjustAssetOutByTokens(
+
766 Rules const& rules,
+
767 STAmount const& balance,
+
768 STAmount const& amount,
+
769 STAmount const& lptAMMBalance,
+
770 STAmount const& tokens,
+
771 std::uint16_t tfee);
+
772
+
776Number
+
777adjustFracByTokens(
+
778 Rules const& rules,
+
779 STAmount const& lptAMMBalance,
+
780 STAmount const& tokens,
+
781 Number const& frac);
+
782
+
783} // namespace ripple
+
784
+
785#endif // RIPPLE_APP_MISC_AMMHELPERS_H_INCLUDED
A generic endpoint for log messages.
Definition: Journal.h:60
Stream error() const
Definition: Journal.h:346
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
Definition: Number.h:393
Definition: Number.h:36
+
rounding_mode
Definition: Number.h:178
@ downward
Definition: Number.h:178
@ upward
Definition: Number.h:178
@ to_nearest
Definition: Number.h:178
@ towards_zero
Definition: Number.h:178
static rounding_mode getround()
Definition: Number.cpp:47
static rounding_mode setround(rounding_mode mode)
Definition: Number.cpp:53
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
+
Definition: STAmount.h:50
+
Issue const & issue() const
Definition: STAmount.h:496
Definition: Number.h:371
T endl(T... args)
+
T minmax(T... args)
+
Number::rounding_mode getAssetRounding(IsDeposit isDeposit)
Definition: AMMHelpers.h:662
+
Number::rounding_mode getLPTokenRounding(IsDeposit isDeposit)
Definition: AMMHelpers.h:654
Number reduceOffer(auto const &amount)
Definition: AMMHelpers.h:40
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
-
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
Definition: AMMHelpers.cpp:41
-
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, bool isDeposit)
Adjust LP tokens to deposit/withdraw.
Definition: AMMHelpers.cpp:133
+
std::pair< STAmount, STAmount > adjustAssetInByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
Definition: AMMHelpers.cpp:349
bool isXRP(AccountID const &c)
Definition: AccountID.h:91
-
std::optional< Number > solveQuadraticEqSmallest(Number const &a, Number const &b, Number const &c)
Solve quadratic equation to find takerGets or takerPays.
Definition: AMMHelpers.cpp:227
+
std::optional< Number > solveQuadraticEqSmallest(Number const &a, Number const &b, Number const &c)
Solve quadratic equation to find takerGets or takerPays.
Definition: AMMHelpers.cpp:271
Issue getIssue(T const &amt)
-
Number solveQuadraticEq(Number const &a, Number const &b, Number const &c)
Positive solution for quadratic equation: x = (-b + sqrt(b**2 + 4*a*c))/(2*a)
Definition: AMMHelpers.cpp:220
-
TOut swapAssetIn(TAmounts< TIn, TOut > const &pool, TIn const &assetIn, std::uint16_t tfee)
AMM pool invariant - the product (A * B) after swap in/out has to remain at least the same: (A + in) ...
Definition: AMMHelpers.h:462
-
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
Definition: AMMHelpers.cpp:90
-
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
Definition: AMMHelpers.cpp:67
-
Number square(Number const &n)
Return square of n.
Definition: AMMHelpers.cpp:127
-
std::tuple< STAmount, std::optional< STAmount >, STAmount > adjustAmountsByLPTokens(STAmount const &amountBalance, STAmount const &amount, std::optional< STAmount > const &amount2, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee, bool isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
Definition: AMMHelpers.cpp:147
-
std::optional< TAmounts< TIn, TOut > > getAMMOfferStartWithTakerGets(TAmounts< TIn, TOut > const &pool, Quality const &targetQuality, std::uint16_t const &tfee)
Generate AMM offer starting with takerGets when AMM pool from the payment perspective is IOU(in)/XRP(...
Definition: AMMHelpers.h:194
+
Number solveQuadraticEq(Number const &a, Number const &b, Number const &c)
Positive solution for quadratic equation: x = (-b + sqrt(b**2 + 4*a*c))/(2*a)
Definition: AMMHelpers.cpp:264
+
std::pair< STAmount, STAmount > adjustAssetOutByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
Definition: AMMHelpers.cpp:375
+
TOut swapAssetIn(TAmounts< TIn, TOut > const &pool, TIn const &assetIn, std::uint16_t tfee)
AMM pool invariant - the product (A * B) after swap in/out has to remain at least the same: (A + in) ...
Definition: AMMHelpers.h:464
+
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
+
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
Definition: AMMHelpers.cpp:80
+
Number square(Number const &n)
Return square of n.
Definition: AMMHelpers.cpp:167
+
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:53
+
std::optional< TAmounts< TIn, TOut > > getAMMOfferStartWithTakerGets(TAmounts< TIn, TOut > const &pool, Quality const &targetQuality, std::uint16_t const &tfee)
Generate AMM offer starting with takerGets when AMM pool from the payment perspective is IOU(in)/XRP(...
Definition: AMMHelpers.h:196
+
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
Definition: AMMHelpers.cpp:311
+
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
Definition: AMMHelpers.cpp:173
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
Definition: AMMCore.h:110
-
std::optional< TAmounts< TIn, TOut > > changeSpotPriceQuality(TAmounts< TIn, TOut > const &pool, Quality const &quality, std::uint16_t tfee, Rules const &rules, beast::Journal j)
Generate AMM offer so that either updated Spot Price Quality (SPQ) is equal to LOB quality (in this c...
Definition: AMMHelpers.h:329
+
std::tuple< STAmount, std::optional< STAmount >, STAmount > adjustAmountsByLPTokens(STAmount const &amountBalance, STAmount const &amount, std::optional< STAmount > const &amount2, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee, IsDeposit isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
Definition: AMMHelpers.cpp:187
+
std::optional< TAmounts< TIn, TOut > > changeSpotPriceQuality(TAmounts< TIn, TOut > const &pool, Quality const &quality, std::uint16_t tfee, Rules const &rules, beast::Journal j)
Generate AMM offer so that either updated Spot Price Quality (SPQ) is equal to LOB quality (in this c...
Definition: AMMHelpers.h:331
+
STAmount ammAssetOut(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
Definition: AMMHelpers.cpp:145
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition: AMMCore.h:101
+
IsDeposit
Definition: AMMHelpers.h:51
+
@ Yes
+
@ No
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition: AMMHelpers.cpp:25
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
std::optional< Rules > const & getCurrentTransactionRules()
Definition: Rules.cpp:47
-
std::optional< TAmounts< TIn, TOut > > getAMMOfferStartWithTakerPays(TAmounts< TIn, TOut > const &pool, Quality const &targetQuality, std::uint16_t tfee)
Generate AMM offer starting with takerPays when AMM pool from the payment perspective is XRP(in)/IOU(...
Definition: AMMHelpers.h:265
-
STAmount withdrawByTokens(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
Definition: AMMHelpers.cpp:114
-
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition: AMMHelpers.h:127
+
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
Definition: AMMHelpers.cpp:112
+
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
Definition: AMMHelpers.cpp:45
+
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition: AMMHelpers.h:678
+
std::optional< TAmounts< TIn, TOut > > getAMMOfferStartWithTakerPays(TAmounts< TIn, TOut > const &pool, Quality const &targetQuality, std::uint16_t tfee)
Generate AMM offer starting with takerPays when AMM pool from the payment perspective is XRP(in)/IOU(...
Definition: AMMHelpers.h:267
+
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition: AMMHelpers.h:129
Number root2(Number f)
Definition: Number.cpp:701
-
TIn swapAssetOut(TAmounts< TIn, TOut > const &pool, TOut const &assetOut, std::uint16_t tfee)
Swap assetOut out of the pool and swap in a proportional amount of the other asset.
Definition: AMMHelpers.h:535
+
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
Definition: AMMHelpers.cpp:401
+
TIn swapAssetOut(TAmounts< TIn, TOut > const &pool, TOut const &assetOut, std::uint16_t tfee)
Swap assetOut out of the pool and swap in a proportional amount of the other asset.
Definition: AMMHelpers.h:537
+
diff --git a/AMMInfo__test_8cpp_source.html b/AMMInfo__test_8cpp_source.html index 1588d43ec5..6fd32975a3 100644 --- a/AMMInfo__test_8cpp_source.html +++ b/AMMInfo__test_8cpp_source.html @@ -281,181 +281,208 @@ $(function() {
203 }
204
205 void
-
206 testVoteAndBid()
+
206 testVoteAndBid(FeatureBitset features)
207 {
208 testcase("Vote and Bid");
209
210 using namespace jtx;
-
211 testAMM([&](AMM& ammAlice, Env& env) {
-
212 BEAST_EXPECT(ammAlice.expectAmmRpcInfo(
-
213 XRP(10000), USD(10000), IOUAmount{10000000, 0}));
-
214 std::unordered_map<std::string, std::uint16_t> votes;
-
215 votes.insert({alice.human(), 0});
-
216 for (int i = 0; i < 7; ++i)
-
217 {
-
218 Account a(std::to_string(i));
-
219 votes.insert({a.human(), 50 * (i + 1)});
-
220 fund(env, gw, {a}, {USD(10000)}, Fund::Acct);
-
221 ammAlice.deposit(a, 10000000);
-
222 ammAlice.vote(a, 50 * (i + 1));
-
223 }
-
224 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
225 Account ed("ed");
-
226 Account bill("bill");
-
227 env.fund(XRP(1000), bob, ed, bill);
-
228 env(ammAlice.bid(
-
229 {.bidMin = 100, .authAccounts = {carol, bob, ed, bill}}));
-
230 BEAST_EXPECT(ammAlice.expectAmmRpcInfo(
-
231 XRP(80000),
-
232 USD(80000),
-
233 IOUAmount{79994400},
-
234 std::nullopt,
-
235 std::nullopt,
-
236 ammAlice.ammAccount()));
-
237 for (auto i = 0; i < 2; ++i)
-
238 {
-
239 std::unordered_set<std::string> authAccounts = {
-
240 carol.human(), bob.human(), ed.human(), bill.human()};
-
241 auto const ammInfo = i ? ammAlice.ammRpcInfo()
-
242 : ammAlice.ammRpcInfo(
-
243 std::nullopt,
-
244 std::nullopt,
-
245 std::nullopt,
-
246 std::nullopt,
-
247 ammAlice.ammAccount());
-
248 auto const& amm = ammInfo[jss::amm];
-
249 try
-
250 {
-
251 // votes
-
252 auto const voteSlots = amm[jss::vote_slots];
-
253 auto votesCopy = votes;
-
254 for (std::uint8_t i = 0; i < 8; ++i)
-
255 {
-
256 if (!BEAST_EXPECT(
-
257 votes[voteSlots[i][jss::account].asString()] ==
-
258 voteSlots[i][jss::trading_fee].asUInt() &&
-
259 voteSlots[i][jss::vote_weight].asUInt() ==
-
260 12500))
-
261 return;
-
262 votes.erase(voteSlots[i][jss::account].asString());
-
263 }
-
264 if (!BEAST_EXPECT(votes.empty()))
-
265 return;
-
266 votes = votesCopy;
-
267
-
268 // bid
-
269 auto const auctionSlot = amm[jss::auction_slot];
-
270 for (std::uint8_t i = 0; i < 4; ++i)
-
271 {
-
272 if (!BEAST_EXPECT(authAccounts.contains(
-
273 auctionSlot[jss::auth_accounts][i][jss::account]
-
274 .asString())))
-
275 return;
-
276 authAccounts.erase(
-
277 auctionSlot[jss::auth_accounts][i][jss::account]
-
278 .asString());
-
279 }
-
280 if (!BEAST_EXPECT(authAccounts.empty()))
-
281 return;
-
282 BEAST_EXPECT(
-
283 auctionSlot[jss::account].asString() == alice.human() &&
-
284 auctionSlot[jss::discounted_fee].asUInt() == 17 &&
-
285 auctionSlot[jss::price][jss::value].asString() ==
-
286 "5600" &&
-
287 auctionSlot[jss::price][jss::currency].asString() ==
-
288 to_string(ammAlice.lptIssue().currency) &&
-
289 auctionSlot[jss::price][jss::issuer].asString() ==
-
290 to_string(ammAlice.lptIssue().account));
-
291 }
-
292 catch (std::exception const& e)
-
293 {
-
294 fail(e.what(), __FILE__, __LINE__);
-
295 }
-
296 }
-
297 });
-
298 }
-
299
-
300 void
-
301 testFreeze()
-
302 {
-
303 using namespace jtx;
-
304 testAMM([&](AMM& ammAlice, Env& env) {
-
305 env(fset(gw, asfGlobalFreeze));
-
306 env.close();
-
307 auto test = [&](bool freeze) {
-
308 auto const info = ammAlice.ammRpcInfo();
-
309 BEAST_EXPECT(
-
310 info[jss::amm][jss::asset2_frozen].asBool() == freeze);
-
311 };
-
312 test(true);
-
313 env(fclear(gw, asfGlobalFreeze));
-
314 env.close();
-
315 test(false);
-
316 });
-
317 }
-
318
-
319 void
-
320 testInvalidAmmField()
-
321 {
-
322 using namespace jtx;
-
323 testcase("Invalid amm field");
-
324
-
325 testAMM([&](AMM& amm, Env&) {
-
326 auto const resp = amm.ammRpcInfo(
-
327 std::nullopt,
-
328 jss::validated.c_str(),
-
329 std::nullopt,
-
330 std::nullopt,
-
331 gw);
-
332 BEAST_EXPECT(
-
333 resp.isMember("error") && resp["error"] == "actNotFound");
-
334 });
-
335 }
-
336
-
337 void
-
338 run() override
-
339 {
-
340 testErrors();
-
341 testSimpleRpc();
-
342 testVoteAndBid();
-
343 testFreeze();
-
344 testInvalidAmmField();
-
345 }
-
346};
-
347
-
348BEAST_DEFINE_TESTSUITE(AMMInfo, app, ripple);
-
349
-
350} // namespace test
-
351} // namespace ripple
+
211 testAMM(
+
212 [&](AMM& ammAlice, Env& env) {
+
213 BEAST_EXPECT(ammAlice.expectAmmRpcInfo(
+
214 XRP(10000), USD(10000), IOUAmount{10000000, 0}));
+
215 std::unordered_map<std::string, std::uint16_t> votes;
+
216 votes.insert({alice.human(), 0});
+
217 for (int i = 0; i < 7; ++i)
+
218 {
+
219 Account a(std::to_string(i));
+
220 votes.insert({a.human(), 50 * (i + 1)});
+
221 if (!features[fixAMMv1_3])
+
222 fund(env, gw, {a}, {USD(10000)}, Fund::Acct);
+
223 else
+
224 fund(env, gw, {a}, {USD(10001)}, Fund::Acct);
+
225 ammAlice.deposit(a, 10000000);
+
226 ammAlice.vote(a, 50 * (i + 1));
+
227 }
+
228 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
229 Account ed("ed");
+
230 Account bill("bill");
+
231 env.fund(XRP(1000), bob, ed, bill);
+
232 env(ammAlice.bid(
+
233 {.bidMin = 100, .authAccounts = {carol, bob, ed, bill}}));
+
234 if (!features[fixAMMv1_3])
+
235 BEAST_EXPECT(ammAlice.expectAmmRpcInfo(
+
236 XRP(80000),
+
237 USD(80000),
+
238 IOUAmount{79994400},
+
239 std::nullopt,
+
240 std::nullopt,
+
241 ammAlice.ammAccount()));
+
242 else
+
243 BEAST_EXPECT(ammAlice.expectAmmRpcInfo(
+
244 XRPAmount(80000000005),
+
245 STAmount{USD, UINT64_C(80'000'00000000005), -11},
+
246 IOUAmount{79994400},
+
247 std::nullopt,
+
248 std::nullopt,
+
249 ammAlice.ammAccount()));
+
250 for (auto i = 0; i < 2; ++i)
+
251 {
+
252 std::unordered_set<std::string> authAccounts = {
+
253 carol.human(), bob.human(), ed.human(), bill.human()};
+
254 auto const ammInfo = i ? ammAlice.ammRpcInfo()
+
255 : ammAlice.ammRpcInfo(
+
256 std::nullopt,
+
257 std::nullopt,
+
258 std::nullopt,
+
259 std::nullopt,
+
260 ammAlice.ammAccount());
+
261 auto const& amm = ammInfo[jss::amm];
+
262 try
+
263 {
+
264 // votes
+
265 auto const voteSlots = amm[jss::vote_slots];
+
266 auto votesCopy = votes;
+
267 for (std::uint8_t i = 0; i < 8; ++i)
+
268 {
+
269 if (!BEAST_EXPECT(
+
270 votes[voteSlots[i][jss::account]
+
271 .asString()] ==
+
272 voteSlots[i][jss::trading_fee]
+
273 .asUInt() &&
+
274 voteSlots[i][jss::vote_weight].asUInt() ==
+
275 12500))
+
276 return;
+
277 votes.erase(voteSlots[i][jss::account].asString());
+
278 }
+
279 if (!BEAST_EXPECT(votes.empty()))
+
280 return;
+
281 votes = votesCopy;
+
282
+
283 // bid
+
284 auto const auctionSlot = amm[jss::auction_slot];
+
285 for (std::uint8_t i = 0; i < 4; ++i)
+
286 {
+
287 if (!BEAST_EXPECT(authAccounts.contains(
+
288 auctionSlot[jss::auth_accounts][i]
+
289 [jss::account]
+
290 .asString())))
+
291 return;
+
292 authAccounts.erase(
+
293 auctionSlot[jss::auth_accounts][i][jss::account]
+
294 .asString());
+
295 }
+
296 if (!BEAST_EXPECT(authAccounts.empty()))
+
297 return;
+
298 BEAST_EXPECT(
+
299 auctionSlot[jss::account].asString() ==
+
300 alice.human() &&
+
301 auctionSlot[jss::discounted_fee].asUInt() == 17 &&
+
302 auctionSlot[jss::price][jss::value].asString() ==
+
303 "5600" &&
+
304 auctionSlot[jss::price][jss::currency].asString() ==
+
305 to_string(ammAlice.lptIssue().currency) &&
+
306 auctionSlot[jss::price][jss::issuer].asString() ==
+
307 to_string(ammAlice.lptIssue().account));
+
308 }
+
309 catch (std::exception const& e)
+
310 {
+
311 fail(e.what(), __FILE__, __LINE__);
+
312 }
+
313 }
+
314 },
+
315 std::nullopt,
+
316 0,
+
317 std::nullopt,
+
318 {features});
+
319 }
+
320
+
321 void
+
322 testFreeze()
+
323 {
+
324 using namespace jtx;
+
325 testAMM([&](AMM& ammAlice, Env& env) {
+
326 env(fset(gw, asfGlobalFreeze));
+
327 env.close();
+
328 auto test = [&](bool freeze) {
+
329 auto const info = ammAlice.ammRpcInfo();
+
330 BEAST_EXPECT(
+
331 info[jss::amm][jss::asset2_frozen].asBool() == freeze);
+
332 };
+
333 test(true);
+
334 env(fclear(gw, asfGlobalFreeze));
+
335 env.close();
+
336 test(false);
+
337 });
+
338 }
+
339
+
340 void
+
341 testInvalidAmmField()
+
342 {
+
343 using namespace jtx;
+
344 testcase("Invalid amm field");
+
345
+
346 testAMM([&](AMM& amm, Env&) {
+
347 auto const resp = amm.ammRpcInfo(
+
348 std::nullopt,
+
349 jss::validated.c_str(),
+
350 std::nullopt,
+
351 std::nullopt,
+
352 gw);
+
353 BEAST_EXPECT(
+
354 resp.isMember("error") && resp["error"] == "actNotFound");
+
355 });
+
356 }
+
357
+
358 void
+
359 run() override
+
360 {
+
361 using namespace jtx;
+
362 auto const all = supported_amendments();
+
363 testErrors();
+
364 testSimpleRpc();
+
365 testVoteAndBid(all);
+
366 testVoteAndBid(all - fixAMMv1_3);
+
367 testFreeze();
+
368 testInvalidAmmField();
+
369 }
+
370};
+
371
+
372BEAST_DEFINE_TESTSUITE(AMMInfo, app, ripple);
+
373
+
374} // namespace test
+
375} // namespace ripple
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition: suite.h:533
+
Definition: Feature.h:147
Floating point representation of amounts with high dynamic range.
Definition: IOUAmount.h:46
AccountID account
Definition: Issue.h:39
Currency currency
Definition: Issue.h:38
+
Definition: STAmount.h:50
+
Definition: XRPAmount.h:43
void testErrors()
-
void testInvalidAmmField()
-
void run() override
Runs the suite.
+
void testInvalidAmmField()
+
void testVoteAndBid(FeatureBitset features)
+
void run() override
Runs the suite.
void testSimpleRpc()
-
void testFreeze()
-
void testVoteAndBid()
-
Definition: AMMTest.h:64
-
jtx::Account const alice
Definition: AMMTest.h:68
-
jtx::IOU const USD
Definition: AMMTest.h:70
-
jtx::Account const gw
Definition: AMMTest.h:66
-
jtx::Account const bob
Definition: AMMTest.h:69
-
jtx::Account const carol
Definition: AMMTest.h:67
-
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> &&cb, std::optional< std::pair< STAmount, STAmount > > const &pool=std::nullopt, std::uint16_t tfee=0, std::optional< jtx::ter > const &ter=std::nullopt, std::vector< FeatureBitset > const &features={supported_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
Definition: AMMTest.cpp:102
+
void testFreeze()
+
Definition: AMMTest.h:73
+
jtx::Account const alice
Definition: AMMTest.h:77
+
jtx::IOU const USD
Definition: AMMTest.h:79
+
jtx::Account const gw
Definition: AMMTest.h:75
+
jtx::Account const bob
Definition: AMMTest.h:78
+
jtx::Account const carol
Definition: AMMTest.h:76
+
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> &&cb, std::optional< std::pair< STAmount, STAmount > > const &pool=std::nullopt, std::uint16_t tfee=0, std::optional< jtx::ter > const &ter=std::nullopt, std::vector< FeatureBitset > const &features={supported_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
Definition: AMMTest.cpp:103
Convenience class to test AMM functionality.
Definition: AMM.h:124
-
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
Definition: AMM.cpp:161
-
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:637
-
AccountID const & ammAccount() const
Definition: AMM.h:325
-
bool expectTradingFee(std::uint16_t fee) const
Definition: AMM.cpp:312
-
Issue lptIssue() const
Definition: AMM.h:331
-
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:411
-
Json::Value bid(BidArg const &arg)
Definition: AMM.cpp:664
-
bool expectAmmRpcInfo(STAmount const &asset1, STAmount const &asset2, IOUAmount const &balance, std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledger_index=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt) const
Definition: AMM.cpp:328
+
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
Definition: AMM.cpp:166
+
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:642
+
AccountID const & ammAccount() const
Definition: AMM.h:331
+
bool expectTradingFee(std::uint16_t fee) const
Definition: AMM.cpp:317
+
Issue lptIssue() const
Definition: AMM.h:337
+
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:416
+
Json::Value bid(BidArg const &arg)
Definition: AMM.cpp:669
+
bool expectAmmRpcInfo(STAmount const &asset1, STAmount const &asset2, IOUAmount const &balance, std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledger_index=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt) const
Definition: AMM.cpp:333
Immutable cryptographic account descriptor.
Definition: Account.h:39
AccountID id() const
Returns the Account ID.
Definition: Account.h:107
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
@@ -471,12 +498,14 @@ $(function() {
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition: flags.h:41
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition: flags.cpp:29
-
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition: AMMTest.cpp:36
+
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition: AMMTest.cpp:37
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
+
FeatureBitset supported_amendments()
Definition: Env.h:74
@ freeze
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:118
constexpr std::uint32_t asfGlobalFreeze
Definition: TxFlags.h:83
+
@ all
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
diff --git a/AMMLiquidity_8cpp_source.html b/AMMLiquidity_8cpp_source.html index 11f65d5743..92d7283864 100644 --- a/AMMLiquidity_8cpp_source.html +++ b/AMMLiquidity_8cpp_source.html @@ -363,7 +363,7 @@ $(function() {
@ upward
Definition: Number.h:178
A view into a ledger.
Definition: ReadView.h:52
virtual Rules const & rules() const =0
Returns the tx processing rules.
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
Definition: STAmount.h:50
static int const cMaxOffset
Definition: STAmount.h:66
@@ -375,14 +375,14 @@ $(function() {
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
Issue getIssue(T const &amt)
-
TOut swapAssetIn(TAmounts< TIn, TOut > const &pool, TIn const &assetIn, std::uint16_t tfee)
AMM pool invariant - the product (A * B) after swap in/out has to remain at least the same: (A + in) ...
Definition: AMMHelpers.h:462
+
TOut swapAssetIn(TAmounts< TIn, TOut > const &pool, TIn const &assetIn, std::uint16_t tfee)
AMM pool invariant - the product (A * B) after swap in/out has to remain at least the same: (A + in) ...
Definition: AMMHelpers.h:464
@ in
@ out
-
std::optional< TAmounts< TIn, TOut > > changeSpotPriceQuality(TAmounts< TIn, TOut > const &pool, Quality const &quality, std::uint16_t tfee, Rules const &rules, beast::Journal j)
Generate AMM offer so that either updated Spot Price Quality (SPQ) is equal to LOB quality (in this c...
Definition: AMMHelpers.h:329
+
std::optional< TAmounts< TIn, TOut > > changeSpotPriceQuality(TAmounts< TIn, TOut > const &pool, Quality const &quality, std::uint16_t tfee, Rules const &rules, beast::Journal j)
Generate AMM offer so that either updated Spot Price Quality (SPQ) is equal to LOB quality (in this c...
Definition: AMMHelpers.h:331
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
STAmount ammAccountHolds(ReadView const &view, AccountID const &ammAccountID, Issue const &issue)
Returns total amount held by AMM for the given token.
Definition: AMMUtils.cpp:210
-
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition: AMMHelpers.h:127
-
TIn swapAssetOut(TAmounts< TIn, TOut > const &pool, TOut const &assetOut, std::uint16_t tfee)
Swap assetOut out of the pool and swap in a proportional amount of the other asset.
Definition: AMMHelpers.h:535
+
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition: AMMHelpers.h:129
+
TIn swapAssetOut(TAmounts< TIn, TOut > const &pool, TOut const &assetOut, std::uint16_t tfee)
Swap assetOut out of the pool and swap in a proportional amount of the other asset.
Definition: AMMHelpers.h:537
T what(T... args)
diff --git a/AMMLiquidity_8h_source.html b/AMMLiquidity_8h_source.html index 78a7b56325..5cc0253d4d 100644 --- a/AMMLiquidity_8h_source.html +++ b/AMMLiquidity_8h_source.html @@ -222,7 +222,7 @@ $(function() {
A currency issued by an account.
Definition: Issue.h:36
Definition: Number.h:36
A view into a ledger.
Definition: ReadView.h:52
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
diff --git a/AMMOffer_8cpp_source.html b/AMMOffer_8cpp_source.html index 537f345a5c..b54139d22f 100644 --- a/AMMOffer_8cpp_source.html +++ b/AMMOffer_8cpp_source.html @@ -277,11 +277,11 @@ $(function() {
Average quality of a path as a function of out: q(out) = m * out + b, where m = -1 / poolGets,...
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
-
TOut swapAssetIn(TAmounts< TIn, TOut > const &pool, TIn const &assetIn, std::uint16_t tfee)
AMM pool invariant - the product (A * B) after swap in/out has to remain at least the same: (A + in) ...
Definition: AMMHelpers.h:462
+
TOut swapAssetIn(TAmounts< TIn, TOut > const &pool, TIn const &assetIn, std::uint16_t tfee)
AMM pool invariant - the product (A * B) after swap in/out has to remain at least the same: (A + in) ...
Definition: AMMHelpers.h:464
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
std::optional< Rules > const & getCurrentTransactionRules()
Definition: Rules.cpp:47
-
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition: AMMHelpers.h:127
-
TIn swapAssetOut(TAmounts< TIn, TOut > const &pool, TOut const &assetOut, std::uint16_t tfee)
Swap assetOut out of the pool and swap in a proportional amount of the other asset.
Definition: AMMHelpers.h:535
+
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition: AMMHelpers.h:129
+
TIn swapAssetOut(TAmounts< TIn, TOut > const &pool, TOut const &assetOut, std::uint16_t tfee)
Swap assetOut out of the pool and swap in a proportional amount of the other asset.
Definition: AMMHelpers.h:537
diff --git a/AMMTest_8cpp_source.html b/AMMTest_8cpp_source.html index 9f332a10dd..8a6a656bdb 100644 --- a/AMMTest_8cpp_source.html +++ b/AMMTest_8cpp_source.html @@ -97,272 +97,291 @@ $(function() {
19
20#include <test/jtx/AMM.h>
21#include <test/jtx/AMMTest.h>
-
22#include <test/jtx/Env.h>
-
23#include <test/jtx/pay.h>
-
24
-
25#include <xrpld/rpc/RPCHandler.h>
-
26#include <xrpld/rpc/detail/RPCHelpers.h>
-
27
-
28#include <xrpl/protocol/STParsedJSON.h>
-
29#include <xrpl/resource/Fees.h>
-
30
-
31namespace ripple {
-
32namespace test {
-
33namespace jtx {
-
34
-
35void
-
36fund(
-
37 jtx::Env& env,
-
38 jtx::Account const& gw,
-
39 std::vector<jtx::Account> const& accounts,
-
40 std::vector<STAmount> const& amts,
-
41 Fund how)
-
42{
-
43 fund(env, gw, accounts, XRP(30000), amts, how);
-
44}
-
45
-
46void
-
47fund(
-
48 jtx::Env& env,
-
49 std::vector<jtx::Account> const& accounts,
-
50 STAmount const& xrp,
-
51 std::vector<STAmount> const& amts,
-
52 Fund how)
-
53{
-
54 for (auto const& account : accounts)
-
55 {
-
56 if (how == Fund::All || how == Fund::Acct)
-
57 {
-
58 env.fund(xrp, account);
-
59 }
-
60 }
-
61 env.close();
-
62 for (auto const& account : accounts)
-
63 {
-
64 for (auto const& amt : amts)
-
65 {
-
66 env.trust(amt + amt, account);
-
67 env(pay(amt.issue().account, account, amt));
-
68 }
-
69 }
-
70 env.close();
-
71}
-
72
-
73void
-
74fund(
-
75 jtx::Env& env,
-
76 jtx::Account const& gw,
-
77 std::vector<jtx::Account> const& accounts,
-
78 STAmount const& xrp,
-
79 std::vector<STAmount> const& amts,
-
80 Fund how)
-
81{
-
82 if (how == Fund::All || how == Fund::Gw)
-
83 env.fund(xrp, gw);
-
84 env.close();
-
85 fund(env, accounts, xrp, amts, how);
-
86}
-
87
-
88AMMTestBase::AMMTestBase()
-
89 : gw("gateway")
-
90 , carol("carol")
-
91 , alice("alice")
-
92 , bob("bob")
-
93 , USD(gw["USD"])
-
94 , EUR(gw["EUR"])
-
95 , GBP(gw["GBP"])
-
96 , BTC(gw["BTC"])
-
97 , BAD(jtx::IOU(gw, badCurrency()))
-
98{
-
99}
-
100
-
101void
-
102AMMTestBase::testAMM(
-
103 std::function<void(jtx::AMM&, jtx::Env&)>&& cb,
-
104 std::optional<std::pair<STAmount, STAmount>> const& pool,
-
105 std::uint16_t tfee,
-
106 std::optional<jtx::ter> const& ter,
-
107 std::vector<FeatureBitset> const& vfeatures)
-
108{
-
109 using namespace jtx;
-
110
-
111 for (auto const& features : vfeatures)
-
112 {
-
113 Env env{*this, features};
-
114
-
115 auto const [asset1, asset2] =
-
116 pool ? *pool : std::make_pair(XRP(10000), USD(10000));
-
117 auto tofund = [&](STAmount const& a) -> STAmount {
-
118 if (a.native())
-
119 {
-
120 auto const defXRP = XRP(30000);
-
121 if (a <= defXRP)
-
122 return defXRP;
-
123 return a + XRP(1000);
-
124 }
-
125 auto const defIOU = STAmount{a.issue(), 30000};
-
126 if (a <= defIOU)
-
127 return defIOU;
-
128 return a + STAmount{a.issue(), 1000};
-
129 };
-
130 auto const toFund1 = tofund(asset1);
-
131 auto const toFund2 = tofund(asset2);
-
132 BEAST_EXPECT(asset1 <= toFund1 && asset2 <= toFund2);
-
133
-
134 if (!asset1.native() && !asset2.native())
-
135 fund(env, gw, {alice, carol}, {toFund1, toFund2}, Fund::All);
-
136 else if (asset1.native())
-
137 fund(env, gw, {alice, carol}, toFund1, {toFund2}, Fund::All);
-
138 else if (asset2.native())
-
139 fund(env, gw, {alice, carol}, toFund2, {toFund1}, Fund::All);
-
140
-
141 AMM ammAlice(
-
142 env,
-
143 alice,
-
144 asset1,
-
145 asset2,
-
146 CreateArg{.log = false, .tfee = tfee, .err = ter});
-
147 if (BEAST_EXPECT(
-
148 ammAlice.expectBalances(asset1, asset2, ammAlice.tokens())))
-
149 cb(ammAlice, env);
-
150 }
-
151}
-
152
-
153XRPAmount
-
154AMMTest::reserve(jtx::Env& env, std::uint32_t count) const
-
155{
-
156 return env.current()->fees().accountReserve(count);
-
157}
-
158
-
159XRPAmount
-
160AMMTest::ammCrtFee(jtx::Env& env) const
-
161{
-
162 return env.current()->fees().increment;
-
163}
-
164
-
165jtx::Env
-
166AMMTest::pathTestEnv()
-
167{
-
168 // These tests were originally written with search parameters that are
-
169 // different from the current defaults. This function creates an env
-
170 // with the search parameters that the tests were written for.
-
171 return Env(*this, envconfig([](std::unique_ptr<Config> cfg) {
-
172 cfg->PATH_SEARCH_OLD = 7;
-
173 cfg->PATH_SEARCH = 7;
-
174 cfg->PATH_SEARCH_MAX = 10;
-
175 return cfg;
-
176 }));
-
177}
-
178
-
179Json::Value
-
180AMMTest::find_paths_request(
-
181 jtx::Env& env,
-
182 jtx::Account const& src,
-
183 jtx::Account const& dst,
-
184 STAmount const& saDstAmount,
-
185 std::optional<STAmount> const& saSendMax,
-
186 std::optional<Currency> const& saSrcCurrency)
-
187{
-
188 using namespace jtx;
-
189
-
190 auto& app = env.app();
-
191 Resource::Charge loadType = Resource::feeReferenceRPC;
-
192 Resource::Consumer c;
-
193
-
194 RPC::JsonContext context{
-
195 {env.journal,
-
196 app,
-
197 loadType,
-
198 app.getOPs(),
-
199 app.getLedgerMaster(),
-
200 c,
-
201 Role::USER,
-
202 {},
-
203 {},
-
204 RPC::apiVersionIfUnspecified},
-
205 {},
-
206 {}};
-
207
-
208 Json::Value params = Json::objectValue;
-
209 params[jss::command] = "ripple_path_find";
-
210 params[jss::source_account] = toBase58(src);
-
211 params[jss::destination_account] = toBase58(dst);
-
212 params[jss::destination_amount] = saDstAmount.getJson(JsonOptions::none);
-
213 if (saSendMax)
-
214 params[jss::send_max] = saSendMax->getJson(JsonOptions::none);
-
215 if (saSrcCurrency)
-
216 {
-
217 auto& sc = params[jss::source_currencies] = Json::arrayValue;
-
218 Json::Value j = Json::objectValue;
-
219 j[jss::currency] = to_string(saSrcCurrency.value());
-
220 sc.append(j);
-
221 }
-
222
-
223 Json::Value result;
-
224 gate g;
-
225 app.getJobQueue().postCoro(jtCLIENT, "RPC-Client", [&](auto const& coro) {
-
226 context.params = std::move(params);
-
227 context.coro = coro;
-
228 RPC::doCommand(context, result);
-
229 g.signal();
-
230 });
-
231
-
232 using namespace std::chrono_literals;
-
233 BEAST_EXPECT(g.wait_for(5s));
-
234 BEAST_EXPECT(!result.isMember(jss::error));
-
235 return result;
-
236}
-
237
-
238std::tuple<STPathSet, STAmount, STAmount>
-
239AMMTest::find_paths(
-
240 jtx::Env& env,
-
241 jtx::Account const& src,
-
242 jtx::Account const& dst,
-
243 STAmount const& saDstAmount,
-
244 std::optional<STAmount> const& saSendMax,
-
245 std::optional<Currency> const& saSrcCurrency)
-
246{
-
247 Json::Value result = find_paths_request(
-
248 env, src, dst, saDstAmount, saSendMax, saSrcCurrency);
-
249 BEAST_EXPECT(!result.isMember(jss::error));
-
250
-
251 STAmount da;
-
252 if (result.isMember(jss::destination_amount))
-
253 da = amountFromJson(sfGeneric, result[jss::destination_amount]);
+
22#include <test/jtx/CaptureLogs.h>
+
23#include <test/jtx/Env.h>
+
24#include <test/jtx/pay.h>
+
25
+
26#include <xrpld/rpc/RPCHandler.h>
+
27#include <xrpld/rpc/detail/RPCHelpers.h>
+
28
+
29#include <xrpl/protocol/STParsedJSON.h>
+
30#include <xrpl/resource/Fees.h>
+
31
+
32namespace ripple {
+
33namespace test {
+
34namespace jtx {
+
35
+
36void
+
37fund(
+
38 jtx::Env& env,
+
39 jtx::Account const& gw,
+
40 std::vector<jtx::Account> const& accounts,
+
41 std::vector<STAmount> const& amts,
+
42 Fund how)
+
43{
+
44 fund(env, gw, accounts, XRP(30000), amts, how);
+
45}
+
46
+
47void
+
48fund(
+
49 jtx::Env& env,
+
50 std::vector<jtx::Account> const& accounts,
+
51 STAmount const& xrp,
+
52 std::vector<STAmount> const& amts,
+
53 Fund how)
+
54{
+
55 for (auto const& account : accounts)
+
56 {
+
57 if (how == Fund::All || how == Fund::Acct)
+
58 {
+
59 env.fund(xrp, account);
+
60 }
+
61 }
+
62 env.close();
+
63 for (auto const& account : accounts)
+
64 {
+
65 for (auto const& amt : amts)
+
66 {
+
67 env.trust(amt + amt, account);
+
68 env(pay(amt.issue().account, account, amt));
+
69 }
+
70 }
+
71 env.close();
+
72}
+
73
+
74void
+
75fund(
+
76 jtx::Env& env,
+
77 jtx::Account const& gw,
+
78 std::vector<jtx::Account> const& accounts,
+
79 STAmount const& xrp,
+
80 std::vector<STAmount> const& amts,
+
81 Fund how)
+
82{
+
83 if (how == Fund::All || how == Fund::Gw)
+
84 env.fund(xrp, gw);
+
85 env.close();
+
86 fund(env, accounts, xrp, amts, how);
+
87}
+
88
+
89AMMTestBase::AMMTestBase()
+
90 : gw("gateway")
+
91 , carol("carol")
+
92 , alice("alice")
+
93 , bob("bob")
+
94 , USD(gw["USD"])
+
95 , EUR(gw["EUR"])
+
96 , GBP(gw["GBP"])
+
97 , BTC(gw["BTC"])
+
98 , BAD(jtx::IOU(gw, badCurrency()))
+
99{
+
100}
+
101
+
102void
+
103AMMTestBase::testAMM(
+
104 std::function<void(jtx::AMM&, jtx::Env&)>&& cb,
+
105 std::optional<std::pair<STAmount, STAmount>> const& pool,
+
106 std::uint16_t tfee,
+
107 std::optional<jtx::ter> const& ter,
+
108 std::vector<FeatureBitset> const& vfeatures)
+
109{
+
110 testAMM(
+
111 std::move(cb),
+
112 TestAMMArg{
+
113 .pool = pool, .tfee = tfee, .ter = ter, .features = vfeatures});
+
114}
+
115
+
116void
+
117AMMTestBase::testAMM(
+
118 std::function<void(jtx::AMM&, jtx::Env&)>&& cb,
+
119 TestAMMArg const& arg)
+
120{
+
121 using namespace jtx;
+
122
+
123 std::string logs;
+
124
+
125 for (auto const& features : arg.features)
+
126 {
+
127 Env env{
+
128 *this,
+
129 features,
+
130 arg.noLog ? std::make_unique<CaptureLogs>(&logs) : nullptr};
+
131
+
132 auto const [asset1, asset2] =
+
133 arg.pool ? *arg.pool : std::make_pair(XRP(10000), USD(10000));
+
134 auto tofund = [&](STAmount const& a) -> STAmount {
+
135 if (a.native())
+
136 {
+
137 auto const defXRP = XRP(30000);
+
138 if (a <= defXRP)
+
139 return defXRP;
+
140 return a + XRP(1000);
+
141 }
+
142 auto const defIOU = STAmount{a.issue(), 30000};
+
143 if (a <= defIOU)
+
144 return defIOU;
+
145 return a + STAmount{a.issue(), 1000};
+
146 };
+
147 auto const toFund1 = tofund(asset1);
+
148 auto const toFund2 = tofund(asset2);
+
149 BEAST_EXPECT(asset1 <= toFund1 && asset2 <= toFund2);
+
150
+
151 if (!asset1.native() && !asset2.native())
+
152 fund(env, gw, {alice, carol}, {toFund1, toFund2}, Fund::All);
+
153 else if (asset1.native())
+
154 fund(env, gw, {alice, carol}, toFund1, {toFund2}, Fund::All);
+
155 else if (asset2.native())
+
156 fund(env, gw, {alice, carol}, toFund2, {toFund1}, Fund::All);
+
157
+
158 AMM ammAlice(
+
159 env,
+
160 alice,
+
161 asset1,
+
162 asset2,
+
163 CreateArg{.log = false, .tfee = arg.tfee, .err = arg.ter});
+
164 if (BEAST_EXPECT(
+
165 ammAlice.expectBalances(asset1, asset2, ammAlice.tokens())))
+
166 cb(ammAlice, env);
+
167 }
+
168}
+
169
+
170XRPAmount
+
171AMMTest::reserve(jtx::Env& env, std::uint32_t count) const
+
172{
+
173 return env.current()->fees().accountReserve(count);
+
174}
+
175
+
176XRPAmount
+
177AMMTest::ammCrtFee(jtx::Env& env) const
+
178{
+
179 return env.current()->fees().increment;
+
180}
+
181
+
182jtx::Env
+
183AMMTest::pathTestEnv()
+
184{
+
185 // These tests were originally written with search parameters that are
+
186 // different from the current defaults. This function creates an env
+
187 // with the search parameters that the tests were written for.
+
188 return Env(*this, envconfig([](std::unique_ptr<Config> cfg) {
+
189 cfg->PATH_SEARCH_OLD = 7;
+
190 cfg->PATH_SEARCH = 7;
+
191 cfg->PATH_SEARCH_MAX = 10;
+
192 return cfg;
+
193 }));
+
194}
+
195
+
196Json::Value
+
197AMMTest::find_paths_request(
+
198 jtx::Env& env,
+
199 jtx::Account const& src,
+
200 jtx::Account const& dst,
+
201 STAmount const& saDstAmount,
+
202 std::optional<STAmount> const& saSendMax,
+
203 std::optional<Currency> const& saSrcCurrency)
+
204{
+
205 using namespace jtx;
+
206
+
207 auto& app = env.app();
+
208 Resource::Charge loadType = Resource::feeReferenceRPC;
+
209 Resource::Consumer c;
+
210
+
211 RPC::JsonContext context{
+
212 {env.journal,
+
213 app,
+
214 loadType,
+
215 app.getOPs(),
+
216 app.getLedgerMaster(),
+
217 c,
+
218 Role::USER,
+
219 {},
+
220 {},
+
221 RPC::apiVersionIfUnspecified},
+
222 {},
+
223 {}};
+
224
+
225 Json::Value params = Json::objectValue;
+
226 params[jss::command] = "ripple_path_find";
+
227 params[jss::source_account] = toBase58(src);
+
228 params[jss::destination_account] = toBase58(dst);
+
229 params[jss::destination_amount] = saDstAmount.getJson(JsonOptions::none);
+
230 if (saSendMax)
+
231 params[jss::send_max] = saSendMax->getJson(JsonOptions::none);
+
232 if (saSrcCurrency)
+
233 {
+
234 auto& sc = params[jss::source_currencies] = Json::arrayValue;
+
235 Json::Value j = Json::objectValue;
+
236 j[jss::currency] = to_string(saSrcCurrency.value());
+
237 sc.append(j);
+
238 }
+
239
+
240 Json::Value result;
+
241 gate g;
+
242 app.getJobQueue().postCoro(jtCLIENT, "RPC-Client", [&](auto const& coro) {
+
243 context.params = std::move(params);
+
244 context.coro = coro;
+
245 RPC::doCommand(context, result);
+
246 g.signal();
+
247 });
+
248
+
249 using namespace std::chrono_literals;
+
250 BEAST_EXPECT(g.wait_for(5s));
+
251 BEAST_EXPECT(!result.isMember(jss::error));
+
252 return result;
+
253}
254
-
255 STAmount sa;
-
256 STPathSet paths;
-
257 if (result.isMember(jss::alternatives))
-
258 {
-
259 auto const& alts = result[jss::alternatives];
-
260 if (alts.size() > 0)
-
261 {
-
262 auto const& path = alts[0u];
-
263
-
264 if (path.isMember(jss::source_amount))
-
265 sa = amountFromJson(sfGeneric, path[jss::source_amount]);
-
266
-
267 if (path.isMember(jss::destination_amount))
-
268 da = amountFromJson(sfGeneric, path[jss::destination_amount]);
-
269
-
270 if (path.isMember(jss::paths_computed))
-
271 {
-
272 Json::Value p;
-
273 p["Paths"] = path[jss::paths_computed];
-
274 STParsedJSONObject po("generic", p);
-
275 paths = po.object->getFieldPathSet(sfPaths);
-
276 }
-
277 }
-
278 }
-
279
-
280 return std::make_tuple(std::move(paths), std::move(sa), std::move(da));
-
281}
-
282
-
283} // namespace jtx
-
284} // namespace test
-
285} // namespace ripple
+
255std::tuple<STPathSet, STAmount, STAmount>
+
256AMMTest::find_paths(
+
257 jtx::Env& env,
+
258 jtx::Account const& src,
+
259 jtx::Account const& dst,
+
260 STAmount const& saDstAmount,
+
261 std::optional<STAmount> const& saSendMax,
+
262 std::optional<Currency> const& saSrcCurrency)
+
263{
+
264 Json::Value result = find_paths_request(
+
265 env, src, dst, saDstAmount, saSendMax, saSrcCurrency);
+
266 BEAST_EXPECT(!result.isMember(jss::error));
+
267
+
268 STAmount da;
+
269 if (result.isMember(jss::destination_amount))
+
270 da = amountFromJson(sfGeneric, result[jss::destination_amount]);
+
271
+
272 STAmount sa;
+
273 STPathSet paths;
+
274 if (result.isMember(jss::alternatives))
+
275 {
+
276 auto const& alts = result[jss::alternatives];
+
277 if (alts.size() > 0)
+
278 {
+
279 auto const& path = alts[0u];
+
280
+
281 if (path.isMember(jss::source_amount))
+
282 sa = amountFromJson(sfGeneric, path[jss::source_amount]);
+
283
+
284 if (path.isMember(jss::destination_amount))
+
285 da = amountFromJson(sfGeneric, path[jss::destination_amount]);
+
286
+
287 if (path.isMember(jss::paths_computed))
+
288 {
+
289 Json::Value p;
+
290 p["Paths"] = path[jss::paths_computed];
+
291 STParsedJSONObject po("generic", p);
+
292 paths = po.object->getFieldPathSet(sfPaths);
+
293 }
+
294 }
+
295 }
+
296
+
297 return std::make_tuple(std::move(paths), std::move(sa), std::move(da));
+
298}
+
299
+
300} // namespace jtx
+
301} // namespace test
+
302} // namespace ripple
+
Represents a JSON value.
Definition: json_value.h:150
bool isMember(char const *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:962
+
std::string const & arg() const
Return the argument associated with the runner.
Definition: suite.h:288
A consumption charge.
Definition: Charge.h:31
An endpoint that consumes resources.
Definition: Consumer.h:35
Definition: STAmount.h:50
@@ -372,20 +391,20 @@ $(function() {
std::optional< STObject > object
The STObject if the parse was successful.
Definition: STParsedJSON.h:51
Definition: STPathSet.h:178
Definition: XRPAmount.h:43
-
jtx::Account const alice
Definition: AMMTest.h:68
-
jtx::IOU const USD
Definition: AMMTest.h:70
-
jtx::Account const gw
Definition: AMMTest.h:66
-
AMMTestBase()
Definition: AMMTest.cpp:88
-
jtx::Account const carol
Definition: AMMTest.h:67
-
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> &&cb, std::optional< std::pair< STAmount, STAmount > > const &pool=std::nullopt, std::uint16_t tfee=0, std::optional< jtx::ter > const &ter=std::nullopt, std::vector< FeatureBitset > const &features={supported_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
Definition: AMMTest.cpp:102
-
Definition: AMMTest.h:104
-
void signal()
Definition: AMMTest.h:124
-
bool wait_for(std::chrono::duration< Rep, Period > const &rel_time)
Definition: AMMTest.h:115
-
std::tuple< STPathSet, STAmount, STAmount > find_paths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt)
Definition: AMMTest.cpp:239
-
jtx::Env pathTestEnv()
Definition: AMMTest.cpp:166
-
XRPAmount ammCrtFee(jtx::Env &env) const
Definition: AMMTest.cpp:160
-
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Definition: AMMTest.cpp:154
-
Json::Value find_paths_request(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt)
Definition: AMMTest.cpp:180
+
jtx::Account const alice
Definition: AMMTest.h:77
+
jtx::IOU const USD
Definition: AMMTest.h:79
+
jtx::Account const gw
Definition: AMMTest.h:75
+
AMMTestBase()
Definition: AMMTest.cpp:89
+
jtx::Account const carol
Definition: AMMTest.h:76
+
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> &&cb, std::optional< std::pair< STAmount, STAmount > > const &pool=std::nullopt, std::uint16_t tfee=0, std::optional< jtx::ter > const &ter=std::nullopt, std::vector< FeatureBitset > const &features={supported_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
Definition: AMMTest.cpp:103
+
Definition: AMMTest.h:118
+
void signal()
Definition: AMMTest.h:138
+
bool wait_for(std::chrono::duration< Rep, Period > const &rel_time)
Definition: AMMTest.h:129
+
std::tuple< STPathSet, STAmount, STAmount > find_paths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt)
Definition: AMMTest.cpp:256
+
jtx::Env pathTestEnv()
Definition: AMMTest.cpp:183
+
XRPAmount ammCrtFee(jtx::Env &env) const
Definition: AMMTest.cpp:177
+
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Definition: AMMTest.cpp:171
+
Json::Value find_paths_request(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt)
Definition: AMMTest.cpp:197
Convenience class to test AMM functionality.
Definition: AMM.h:124
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:121
@@ -409,7 +428,7 @@ $(function() {
static constexpr auto apiVersionIfUnspecified
Definition: ApiVersion.h:59
Charge const feeReferenceRPC
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
-
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition: AMMTest.cpp:36
+
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition: AMMTest.cpp:37
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
Fund
Definition: AMMTest.h:36
@@ -430,6 +449,8 @@ $(function() {
Definition: Context.h:53
Definition: AMM.h:63
bool log
Definition: AMM.h:64
+
Definition: AMMTest.h:39
+
std::optional< std::pair< STAmount, STAmount > > pool
Definition: AMMTest.h:40
T value(T... args)
diff --git a/AMMTest_8h_source.html b/AMMTest_8h_source.html index a48bdf9976..b3891569e0 100644 --- a/AMMTest_8h_source.html +++ b/AMMTest_8h_source.html @@ -113,152 +113,167 @@ $(function() {
35
36enum class Fund { All, Acct, Gw, IOUOnly };
37
-
38void
-
39fund(
-
40 jtx::Env& env,
-
41 jtx::Account const& gw,
-
42 std::vector<jtx::Account> const& accounts,
-
43 std::vector<STAmount> const& amts,
-
44 Fund how);
-
45
-
46void
-
47fund(
-
48 jtx::Env& env,
-
49 jtx::Account const& gw,
-
50 std::vector<jtx::Account> const& accounts,
-
51 STAmount const& xrp,
-
52 std::vector<STAmount> const& amts = {},
-
53 Fund how = Fund::All);
+
38struct TestAMMArg
+
39{
+
40 std::optional<std::pair<STAmount, STAmount>> pool = std::nullopt;
+
41 std::uint16_t tfee = 0;
+
42 std::optional<jtx::ter> ter = std::nullopt;
+
43 std::vector<FeatureBitset> features = {supported_amendments()};
+
44 bool noLog = false;
+
45};
+
46
+
47void
+
48fund(
+
49 jtx::Env& env,
+
50 jtx::Account const& gw,
+
51 std::vector<jtx::Account> const& accounts,
+
52 std::vector<STAmount> const& amts,
+
53 Fund how);
54
55void
56fund(
-
57 jtx::Env& env,
-
58 std::vector<jtx::Account> const& accounts,
-
59 STAmount const& xrp,
-
60 std::vector<STAmount> const& amts = {},
-
61 Fund how = Fund::All);
-
62
-
63class AMMTestBase : public beast::unit_test::suite
-
64{
-
65protected:
-
66 jtx::Account const gw;
-
67 jtx::Account const carol;
-
68 jtx::Account const alice;
-
69 jtx::Account const bob;
-
70 jtx::IOU const USD;
-
71 jtx::IOU const EUR;
-
72 jtx::IOU const GBP;
-
73 jtx::IOU const BTC;
-
74 jtx::IOU const BAD;
-
75
-
76public:
-
77 AMMTestBase();
-
78
-
79protected:
-
83 void
-
84 testAMM(
-
85 std::function<void(jtx::AMM&, jtx::Env&)>&& cb,
-
86 std::optional<std::pair<STAmount, STAmount>> const& pool = std::nullopt,
-
87 std::uint16_t tfee = 0,
-
88 std::optional<jtx::ter> const& ter = std::nullopt,
-
89 std::vector<FeatureBitset> const& features = {supported_amendments()});
-
90};
-
91
-
92class AMMTest : public jtx::AMMTestBase
-
93{
-
94protected:
-
95 XRPAmount
-
96 reserve(jtx::Env& env, std::uint32_t count) const;
-
97
-
98 XRPAmount
-
99 ammCrtFee(jtx::Env& env) const;
-
100
-
101 /* Path_test */
-
102 /************************************************/
-
103 class gate
-
104 {
-
105 private:
-
106 std::condition_variable cv_;
-
107 std::mutex mutex_;
-
108 bool signaled_ = false;
-
109
-
110 public:
-
111 // Thread safe, blocks until signaled or period expires.
-
112 // Returns `true` if signaled.
-
113 template <class Rep, class Period>
-
114 bool
-
115 wait_for(std::chrono::duration<Rep, Period> const& rel_time)
-
116 {
-
117 std::unique_lock<std::mutex> lk(mutex_);
-
118 auto b = cv_.wait_for(lk, rel_time, [this] { return signaled_; });
-
119 signaled_ = false;
-
120 return b;
-
121 }
-
122
-
123 void
-
124 signal()
-
125 {
-
126 std::lock_guard lk(mutex_);
-
127 signaled_ = true;
-
128 cv_.notify_all();
-
129 }
-
130 };
-
131
-
132 jtx::Env
-
133 pathTestEnv();
-
134
-
135 Json::Value
-
136 find_paths_request(
-
137 jtx::Env& env,
-
138 jtx::Account const& src,
-
139 jtx::Account const& dst,
-
140 STAmount const& saDstAmount,
-
141 std::optional<STAmount> const& saSendMax = std::nullopt,
-
142 std::optional<Currency> const& saSrcCurrency = std::nullopt);
-
143
-
144 std::tuple<STPathSet, STAmount, STAmount>
-
145 find_paths(
-
146 jtx::Env& env,
-
147 jtx::Account const& src,
-
148 jtx::Account const& dst,
-
149 STAmount const& saDstAmount,
-
150 std::optional<STAmount> const& saSendMax = std::nullopt,
-
151 std::optional<Currency> const& saSrcCurrency = std::nullopt);
-
152};
-
153
-
154} // namespace jtx
-
155} // namespace test
-
156} // namespace ripple
+
57 jtx::Env& env,
+
58 jtx::Account const& gw,
+
59 std::vector<jtx::Account> const& accounts,
+
60 STAmount const& xrp,
+
61 std::vector<STAmount> const& amts = {},
+
62 Fund how = Fund::All);
+
63
+
64void
+
65fund(
+
66 jtx::Env& env,
+
67 std::vector<jtx::Account> const& accounts,
+
68 STAmount const& xrp,
+
69 std::vector<STAmount> const& amts = {},
+
70 Fund how = Fund::All);
+
71
+
72class AMMTestBase : public beast::unit_test::suite
+
73{
+
74protected:
+
75 jtx::Account const gw;
+
76 jtx::Account const carol;
+
77 jtx::Account const alice;
+
78 jtx::Account const bob;
+
79 jtx::IOU const USD;
+
80 jtx::IOU const EUR;
+
81 jtx::IOU const GBP;
+
82 jtx::IOU const BTC;
+
83 jtx::IOU const BAD;
+
84
+
85public:
+
86 AMMTestBase();
+
87
+
88protected:
+
92 void
+
93 testAMM(
+
94 std::function<void(jtx::AMM&, jtx::Env&)>&& cb,
+
95 std::optional<std::pair<STAmount, STAmount>> const& pool = std::nullopt,
+
96 std::uint16_t tfee = 0,
+
97 std::optional<jtx::ter> const& ter = std::nullopt,
+
98 std::vector<FeatureBitset> const& features = {supported_amendments()});
+
99
+
100 void
+
101 testAMM(
+
102 std::function<void(jtx::AMM&, jtx::Env&)>&& cb,
+
103 TestAMMArg const& arg);
+
104};
+
105
+
106class AMMTest : public jtx::AMMTestBase
+
107{
+
108protected:
+
109 XRPAmount
+
110 reserve(jtx::Env& env, std::uint32_t count) const;
+
111
+
112 XRPAmount
+
113 ammCrtFee(jtx::Env& env) const;
+
114
+
115 /* Path_test */
+
116 /************************************************/
+
117 class gate
+
118 {
+
119 private:
+
120 std::condition_variable cv_;
+
121 std::mutex mutex_;
+
122 bool signaled_ = false;
+
123
+
124 public:
+
125 // Thread safe, blocks until signaled or period expires.
+
126 // Returns `true` if signaled.
+
127 template <class Rep, class Period>
+
128 bool
+
129 wait_for(std::chrono::duration<Rep, Period> const& rel_time)
+
130 {
+
131 std::unique_lock<std::mutex> lk(mutex_);
+
132 auto b = cv_.wait_for(lk, rel_time, [this] { return signaled_; });
+
133 signaled_ = false;
+
134 return b;
+
135 }
+
136
+
137 void
+
138 signal()
+
139 {
+
140 std::lock_guard lk(mutex_);
+
141 signaled_ = true;
+
142 cv_.notify_all();
+
143 }
+
144 };
+
145
+
146 jtx::Env
+
147 pathTestEnv();
+
148
+
149 Json::Value
+
150 find_paths_request(
+
151 jtx::Env& env,
+
152 jtx::Account const& src,
+
153 jtx::Account const& dst,
+
154 STAmount const& saDstAmount,
+
155 std::optional<STAmount> const& saSendMax = std::nullopt,
+
156 std::optional<Currency> const& saSrcCurrency = std::nullopt);
157
-
158#endif // RIPPLE_TEST_JTX_AMMTEST_H_INCLUDED
+
158 std::tuple<STPathSet, STAmount, STAmount>
+
159 find_paths(
+
160 jtx::Env& env,
+
161 jtx::Account const& src,
+
162 jtx::Account const& dst,
+
163 STAmount const& saDstAmount,
+
164 std::optional<STAmount> const& saSendMax = std::nullopt,
+
165 std::optional<Currency> const& saSrcCurrency = std::nullopt);
+
166};
+
167
+
168} // namespace jtx
+
169} // namespace test
+
170} // namespace ripple
+
171
+
172#endif // RIPPLE_TEST_JTX_AMMTEST_H_INCLUDED
Represents a JSON value.
Definition: json_value.h:150
A testsuite class.
Definition: suite.h:55
+
std::string const & arg() const
Return the argument associated with the runner.
Definition: suite.h:288
Definition: STAmount.h:50
Definition: XRPAmount.h:43
-
Definition: AMMTest.h:64
-
jtx::Account const alice
Definition: AMMTest.h:68
-
jtx::IOU const BTC
Definition: AMMTest.h:73
-
jtx::IOU const USD
Definition: AMMTest.h:70
-
jtx::Account const gw
Definition: AMMTest.h:66
-
AMMTestBase()
Definition: AMMTest.cpp:88
-
jtx::Account const bob
Definition: AMMTest.h:69
-
jtx::IOU const EUR
Definition: AMMTest.h:71
-
jtx::IOU const GBP
Definition: AMMTest.h:72
-
jtx::Account const carol
Definition: AMMTest.h:67
-
jtx::IOU const BAD
Definition: AMMTest.h:74
-
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> &&cb, std::optional< std::pair< STAmount, STAmount > > const &pool=std::nullopt, std::uint16_t tfee=0, std::optional< jtx::ter > const &ter=std::nullopt, std::vector< FeatureBitset > const &features={supported_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
Definition: AMMTest.cpp:102
-
Definition: AMMTest.h:104
-
std::mutex mutex_
Definition: AMMTest.h:107
-
void signal()
Definition: AMMTest.h:124
-
bool signaled_
Definition: AMMTest.h:108
-
bool wait_for(std::chrono::duration< Rep, Period > const &rel_time)
Definition: AMMTest.h:115
-
std::condition_variable cv_
Definition: AMMTest.h:106
-
Definition: AMMTest.h:93
-
std::tuple< STPathSet, STAmount, STAmount > find_paths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt)
Definition: AMMTest.cpp:239
-
jtx::Env pathTestEnv()
Definition: AMMTest.cpp:166
-
XRPAmount ammCrtFee(jtx::Env &env) const
Definition: AMMTest.cpp:160
-
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Definition: AMMTest.cpp:154
-
Json::Value find_paths_request(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt)
Definition: AMMTest.cpp:180
+
Definition: AMMTest.h:73
+
jtx::Account const alice
Definition: AMMTest.h:77
+
jtx::IOU const BTC
Definition: AMMTest.h:82
+
jtx::IOU const USD
Definition: AMMTest.h:79
+
jtx::Account const gw
Definition: AMMTest.h:75
+
AMMTestBase()
Definition: AMMTest.cpp:89
+
jtx::Account const bob
Definition: AMMTest.h:78
+
jtx::IOU const EUR
Definition: AMMTest.h:80
+
jtx::IOU const GBP
Definition: AMMTest.h:81
+
jtx::Account const carol
Definition: AMMTest.h:76
+
jtx::IOU const BAD
Definition: AMMTest.h:83
+
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> &&cb, std::optional< std::pair< STAmount, STAmount > > const &pool=std::nullopt, std::uint16_t tfee=0, std::optional< jtx::ter > const &ter=std::nullopt, std::vector< FeatureBitset > const &features={supported_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
Definition: AMMTest.cpp:103
+
Definition: AMMTest.h:118
+
std::mutex mutex_
Definition: AMMTest.h:121
+
void signal()
Definition: AMMTest.h:138
+
bool signaled_
Definition: AMMTest.h:122
+
bool wait_for(std::chrono::duration< Rep, Period > const &rel_time)
Definition: AMMTest.h:129
+
std::condition_variable cv_
Definition: AMMTest.h:120
+
Definition: AMMTest.h:107
+
std::tuple< STPathSet, STAmount, STAmount > find_paths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt)
Definition: AMMTest.cpp:256
+
jtx::Env pathTestEnv()
Definition: AMMTest.cpp:183
+
XRPAmount ammCrtFee(jtx::Env &env) const
Definition: AMMTest.cpp:177
+
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Definition: AMMTest.cpp:171
+
Json::Value find_paths_request(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt)
Definition: AMMTest.cpp:197
Convenience class to test AMM functionality.
Definition: AMM.h:124
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:121
@@ -270,7 +285,7 @@ $(function() {
-
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition: AMMTest.cpp:36
+
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition: AMMTest.cpp:37
FeatureBitset supported_amendments()
Definition: Env.h:74
Fund
Definition: AMMTest.h:36
@ Gw
@@ -282,6 +297,11 @@ $(function() {
T notify_all(T... args)
+
Definition: AMMTest.h:39
+
std::vector< FeatureBitset > features
Definition: AMMTest.h:43
+
std::optional< std::pair< STAmount, STAmount > > pool
Definition: AMMTest.h:40
+
bool noLog
Definition: AMMTest.h:44
+
std::uint16_t tfee
Definition: AMMTest.h:41
diff --git a/AMMWithdraw_8cpp_source.html b/AMMWithdraw_8cpp_source.html index 14e7c0bb31..237995293e 100644 --- a/AMMWithdraw_8cpp_source.html +++ b/AMMWithdraw_8cpp_source.html @@ -593,14 +593,14 @@ $(function() {
515 [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] =
516 [&]() -> std::tuple<STAmount, std::optional<STAmount>, STAmount> {
517 if (withdrawAll == WithdrawAll::No)
-
518 return adjustAmountsByLPTokens(
+
518 return adjustAmountsByLPTokens(
519 amountBalance,
520 amountWithdraw,
521 amount2Withdraw,
522 lpTokensAMMBalance,
523 lpTokensWithdraw,
524 tfee,
-
525 false);
+
525 IsDeposit::No);
526 return std::make_tuple(
527 amountWithdraw, amount2Withdraw, lpTokensWithdraw);
528 }();
@@ -761,315 +761,392 @@ $(function() {
683 amount2WithdrawActual);
684}
685
-
686std::pair<TER, STAmount>
-
687AMMWithdraw::equalWithdrawTokens(
-
688 Sandbox& view,
-
689 SLE const& ammSle,
-
690 AccountID const& ammAccount,
-
691 STAmount const& amountBalance,
-
692 STAmount const& amount2Balance,
-
693 STAmount const& lptAMMBalance,
-
694 STAmount const& lpTokens,
-
695 STAmount const& lpTokensWithdraw,
-
696 std::uint16_t tfee)
-
697{
-
698 TER ter;
-
699 STAmount newLPTokenBalance;
-
700 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) =
-
701 equalWithdrawTokens(
-
702 view,
-
703 ammSle,
-
704 account_,
-
705 ammAccount,
-
706 amountBalance,
-
707 amount2Balance,
-
708 lptAMMBalance,
-
709 lpTokens,
-
710 lpTokensWithdraw,
-
711 tfee,
-
712 FreezeHandling::fhZERO_IF_FROZEN,
-
713 isWithdrawAll(ctx_.tx),
-
714 mPriorBalance,
-
715 ctx_.journal);
-
716 return {ter, newLPTokenBalance};
-
717}
-
718
-
719std::pair<TER, bool>
-
720AMMWithdraw::deleteAMMAccountIfEmpty(
-
721 Sandbox& sb,
-
722 std::shared_ptr<SLE> const ammSle,
-
723 STAmount const& lpTokenBalance,
-
724 Issue const& issue1,
-
725 Issue const& issue2,
-
726 beast::Journal const& journal)
-
727{
-
728 TER ter;
-
729 bool updateBalance = true;
-
730 if (lpTokenBalance == beast::zero)
-
731 {
-
732 ter = deleteAMMAccount(sb, issue1, issue2, journal);
-
733 if (ter != tesSUCCESS && ter != tecINCOMPLETE)
-
734 return {ter, false}; // LCOV_EXCL_LINE
-
735 else
-
736 updateBalance = (ter == tecINCOMPLETE);
-
737 }
-
738
-
739 if (updateBalance)
-
740 {
-
741 ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
-
742 sb.update(ammSle);
-
743 }
-
744
-
745 return {ter, true};
-
746}
-
747
-
750std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
-
751AMMWithdraw::equalWithdrawTokens(
-
752 Sandbox& view,
-
753 SLE const& ammSle,
-
754 AccountID const account,
-
755 AccountID const& ammAccount,
-
756 STAmount const& amountBalance,
-
757 STAmount const& amount2Balance,
-
758 STAmount const& lptAMMBalance,
-
759 STAmount const& lpTokens,
-
760 STAmount const& lpTokensWithdraw,
-
761 std::uint16_t tfee,
-
762 FreezeHandling freezeHanding,
-
763 WithdrawAll withdrawAll,
-
764 XRPAmount const& priorBalance,
-
765 beast::Journal const& journal)
-
766{
-
767 try
-
768 {
-
769 // Withdrawing all tokens in the pool
-
770 if (lpTokensWithdraw == lptAMMBalance)
-
771 {
-
772 return withdraw(
-
773 view,
-
774 ammSle,
-
775 ammAccount,
-
776 account,
-
777 amountBalance,
-
778 amountBalance,
-
779 amount2Balance,
-
780 lptAMMBalance,
-
781 lpTokensWithdraw,
-
782 tfee,
-
783 freezeHanding,
-
784 WithdrawAll::Yes,
-
785 priorBalance,
-
786 journal);
-
787 }
-
788
-
789 auto const frac = divide(lpTokensWithdraw, lptAMMBalance, noIssue());
-
790 auto const withdrawAmount =
-
791 multiply(amountBalance, frac, amountBalance.issue());
-
792 auto const withdraw2Amount =
-
793 multiply(amount2Balance, frac, amount2Balance.issue());
-
794 // LP is making equal withdrawal by tokens but the requested amount
-
795 // of LP tokens is likely too small and results in one-sided pool
-
796 // withdrawal due to round off. Fail so the user withdraws
-
797 // more tokens.
-
798 if (withdrawAmount == beast::zero || withdraw2Amount == beast::zero)
-
799 return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
-
800
-
801 return withdraw(
-
802 view,
-
803 ammSle,
-
804 ammAccount,
-
805 account,
-
806 amountBalance,
-
807 withdrawAmount,
-
808 withdraw2Amount,
-
809 lptAMMBalance,
-
810 lpTokensWithdraw,
-
811 tfee,
-
812 freezeHanding,
-
813 withdrawAll,
-
814 priorBalance,
-
815 journal);
-
816 }
-
817 // LCOV_EXCL_START
-
818 catch (std::exception const& e)
-
819 {
-
820 JLOG(journal.error())
-
821 << "AMMWithdraw::equalWithdrawTokens exception " << e.what();
-
822 }
-
823 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
-
824 // LCOV_EXCL_STOP
-
825}
-
826
-
852std::pair<TER, STAmount>
-
853AMMWithdraw::equalWithdrawLimit(
-
854 Sandbox& view,
-
855 SLE const& ammSle,
-
856 AccountID const& ammAccount,
-
857 STAmount const& amountBalance,
-
858 STAmount const& amount2Balance,
-
859 STAmount const& lptAMMBalance,
-
860 STAmount const& amount,
-
861 STAmount const& amount2,
-
862 std::uint16_t tfee)
-
863{
-
864 auto frac = Number{amount} / amountBalance;
-
865 auto const amount2Withdraw = amount2Balance * frac;
-
866 if (amount2Withdraw <= amount2)
-
867 {
-
868 return withdraw(
-
869 view,
-
870 ammSle,
-
871 ammAccount,
-
872 amountBalance,
-
873 amount,
-
874 toSTAmount(amount2.issue(), amount2Withdraw),
-
875 lptAMMBalance,
-
876 toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
-
877 tfee);
-
878 }
-
879
-
880 frac = Number{amount2} / amount2Balance;
-
881 auto const amountWithdraw = amountBalance * frac;
-
882 XRPL_ASSERT(
-
883 amountWithdraw <= amount,
-
884 "ripple::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
-
885 return withdraw(
-
886 view,
-
887 ammSle,
-
888 ammAccount,
-
889 amountBalance,
-
890 toSTAmount(amount.issue(), amountWithdraw),
-
891 amount2,
-
892 lptAMMBalance,
-
893 toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
-
894 tfee);
-
895}
-
896
-
902std::pair<TER, STAmount>
-
903AMMWithdraw::singleWithdraw(
-
904 Sandbox& view,
-
905 SLE const& ammSle,
-
906 AccountID const& ammAccount,
-
907 STAmount const& amountBalance,
-
908 STAmount const& lptAMMBalance,
-
909 STAmount const& amount,
-
910 std::uint16_t tfee)
-
911{
-
912 auto const tokens = lpTokensOut(amountBalance, amount, lptAMMBalance, tfee);
-
913 if (tokens == beast::zero)
-
914 return {tecAMM_FAILED, STAmount{}};
-
915
-
916 return withdraw(
-
917 view,
-
918 ammSle,
-
919 ammAccount,
-
920 amountBalance,
-
921 amount,
-
922 std::nullopt,
-
923 lptAMMBalance,
-
924 tokens,
-
925 tfee);
-
926}
-
927
-
938std::pair<TER, STAmount>
-
939AMMWithdraw::singleWithdrawTokens(
-
940 Sandbox& view,
-
941 SLE const& ammSle,
-
942 AccountID const& ammAccount,
-
943 STAmount const& amountBalance,
-
944 STAmount const& lptAMMBalance,
-
945 STAmount const& amount,
-
946 STAmount const& lpTokensWithdraw,
-
947 std::uint16_t tfee)
-
948{
-
949 auto const amountWithdraw =
-
950 withdrawByTokens(amountBalance, lptAMMBalance, lpTokensWithdraw, tfee);
-
951 if (amount == beast::zero || amountWithdraw >= amount)
-
952 {
-
953 return withdraw(
-
954 view,
-
955 ammSle,
-
956 ammAccount,
-
957 amountBalance,
-
958 amountWithdraw,
-
959 std::nullopt,
-
960 lptAMMBalance,
-
961 lpTokensWithdraw,
-
962 tfee);
-
963 }
-
964
-
965 return {tecAMM_FAILED, STAmount{}};
-
966}
-
967
-
987std::pair<TER, STAmount>
-
988AMMWithdraw::singleWithdrawEPrice(
-
989 Sandbox& view,
-
990 SLE const& ammSle,
-
991 AccountID const& ammAccount,
-
992 STAmount const& amountBalance,
-
993 STAmount const& lptAMMBalance,
-
994 STAmount const& amount,
-
995 STAmount const& ePrice,
-
996 std::uint16_t tfee)
-
997{
-
998 // LPTokens is asset in => E = t / a and formula (8) is:
-
999 // a = A*(t1**2 + t1*(f - 2))/(t1*f - 1)
-
1000 // substitute a as t/E =>
-
1001 // t/E = A*(t1**2 + t1*(f - 2))/(t1*f - 1), t1=t/T => t = t1*T
-
1002 // t1*T/E = A*((t/T)**2 + t*(f - 2)/T)/(t*f/T - 1) =>
-
1003 // T/E = A*(t1 + f-2)/(t1*f - 1) =>
-
1004 // T*(t1*f - 1) = A*E*(t1 + f - 2) =>
-
1005 // t1*T*f - T = t1*A*E + A*E*(f - 2) =>
-
1006 // t1*(T*f - A*E) = T + A*E*(f - 2) =>
-
1007 // t = T*(T + A*E*(f - 2))/(T*f - A*E)
-
1008 Number const ae = amountBalance * ePrice;
-
1009 auto const f = getFee(tfee);
-
1010 auto const tokens = lptAMMBalance * (lptAMMBalance + ae * (f - 2)) /
-
1011 (lptAMMBalance * f - ae);
-
1012 if (tokens <= 0)
-
1013 return {tecAMM_FAILED, STAmount{}};
-
1014 auto const amountWithdraw = toSTAmount(amount.issue(), tokens / ePrice);
-
1015 if (amount == beast::zero || amountWithdraw >= amount)
-
1016 {
-
1017 return withdraw(
-
1018 view,
-
1019 ammSle,
-
1020 ammAccount,
-
1021 amountBalance,
-
1022 amountWithdraw,
-
1023 std::nullopt,
-
1024 lptAMMBalance,
-
1025 toSTAmount(lptAMMBalance.issue(), tokens),
-
1026 tfee);
-
1027 }
-
1028
-
1029 return {tecAMM_FAILED, STAmount{}};
-
1030}
-
1031
-
1032WithdrawAll
-
1033AMMWithdraw::isWithdrawAll(STTx const& tx)
-
1034{
-
1035 if (tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll))
-
1036 return WithdrawAll::Yes;
-
1037 return WithdrawAll::No;
-
1038}
-
1039} // namespace ripple
+
686static STAmount
+
687adjustLPTokensIn(
+
688 Rules const& rules,
+
689 STAmount const& lptAMMBalance,
+
690 STAmount const& lpTokensWithdraw,
+
691 WithdrawAll withdrawAll)
+
692{
+
693 if (!rules.enabled(fixAMMv1_3) || withdrawAll == WithdrawAll::Yes)
+
694 return lpTokensWithdraw;
+
695 return adjustLPTokens(lptAMMBalance, lpTokensWithdraw, IsDeposit::No);
+
696}
+
697
+
700std::pair<TER, STAmount>
+
701AMMWithdraw::equalWithdrawTokens(
+
702 Sandbox& view,
+
703 SLE const& ammSle,
+
704 AccountID const& ammAccount,
+
705 STAmount const& amountBalance,
+
706 STAmount const& amount2Balance,
+
707 STAmount const& lptAMMBalance,
+
708 STAmount const& lpTokens,
+
709 STAmount const& lpTokensWithdraw,
+
710 std::uint16_t tfee)
+
711{
+
712 TER ter;
+
713 STAmount newLPTokenBalance;
+
714 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) =
+
715 equalWithdrawTokens(
+
716 view,
+
717 ammSle,
+
718 account_,
+
719 ammAccount,
+
720 amountBalance,
+
721 amount2Balance,
+
722 lptAMMBalance,
+
723 lpTokens,
+
724 lpTokensWithdraw,
+
725 tfee,
+
726 FreezeHandling::fhZERO_IF_FROZEN,
+
727 isWithdrawAll(ctx_.tx),
+
728 mPriorBalance,
+
729 ctx_.journal);
+
730 return {ter, newLPTokenBalance};
+
731}
+
732
+
733std::pair<TER, bool>
+
734AMMWithdraw::deleteAMMAccountIfEmpty(
+
735 Sandbox& sb,
+
736 std::shared_ptr<SLE> const ammSle,
+
737 STAmount const& lpTokenBalance,
+
738 Issue const& issue1,
+
739 Issue const& issue2,
+
740 beast::Journal const& journal)
+
741{
+
742 TER ter;
+
743 bool updateBalance = true;
+
744 if (lpTokenBalance == beast::zero)
+
745 {
+
746 ter = deleteAMMAccount(sb, issue1, issue2, journal);
+
747 if (ter != tesSUCCESS && ter != tecINCOMPLETE)
+
748 return {ter, false}; // LCOV_EXCL_LINE
+
749 else
+
750 updateBalance = (ter == tecINCOMPLETE);
+
751 }
+
752
+
753 if (updateBalance)
+
754 {
+
755 ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
+
756 sb.update(ammSle);
+
757 }
+
758
+
759 return {ter, true};
+
760}
+
761
+
764std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
+
765AMMWithdraw::equalWithdrawTokens(
+
766 Sandbox& view,
+
767 SLE const& ammSle,
+
768 AccountID const account,
+
769 AccountID const& ammAccount,
+
770 STAmount const& amountBalance,
+
771 STAmount const& amount2Balance,
+
772 STAmount const& lptAMMBalance,
+
773 STAmount const& lpTokens,
+
774 STAmount const& lpTokensWithdraw,
+
775 std::uint16_t tfee,
+
776 FreezeHandling freezeHanding,
+
777 WithdrawAll withdrawAll,
+
778 XRPAmount const& priorBalance,
+
779 beast::Journal const& journal)
+
780{
+
781 try
+
782 {
+
783 // Withdrawing all tokens in the pool
+
784 if (lpTokensWithdraw == lptAMMBalance)
+
785 {
+
786 return withdraw(
+
787 view,
+
788 ammSle,
+
789 ammAccount,
+
790 account,
+
791 amountBalance,
+
792 amountBalance,
+
793 amount2Balance,
+
794 lptAMMBalance,
+
795 lpTokensWithdraw,
+
796 tfee,
+
797 freezeHanding,
+
798 WithdrawAll::Yes,
+
799 priorBalance,
+
800 journal);
+
801 }
+
802
+
803 auto const tokensAdj = adjustLPTokensIn(
+
804 view.rules(), lptAMMBalance, lpTokensWithdraw, withdrawAll);
+
805 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
+
806 return {
+
807 tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, std::nullopt};
+
808 // the adjusted tokens are factored in
+
809 auto const frac = divide(tokensAdj, lptAMMBalance, noIssue());
+
810 auto const amountWithdraw =
+
811 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
+
812 auto const amount2Withdraw =
+
813 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
+
814 // LP is making equal withdrawal by tokens but the requested amount
+
815 // of LP tokens is likely too small and results in one-sided pool
+
816 // withdrawal due to round off. Fail so the user withdraws
+
817 // more tokens.
+
818 if (amountWithdraw == beast::zero || amount2Withdraw == beast::zero)
+
819 return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
+
820
+
821 return withdraw(
+
822 view,
+
823 ammSle,
+
824 ammAccount,
+
825 account,
+
826 amountBalance,
+
827 amountWithdraw,
+
828 amount2Withdraw,
+
829 lptAMMBalance,
+
830 tokensAdj,
+
831 tfee,
+
832 freezeHanding,
+
833 withdrawAll,
+
834 priorBalance,
+
835 journal);
+
836 }
+
837 // LCOV_EXCL_START
+
838 catch (std::exception const& e)
+
839 {
+
840 JLOG(journal.error())
+
841 << "AMMWithdraw::equalWithdrawTokens exception " << e.what();
+
842 }
+
843 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
+
844 // LCOV_EXCL_STOP
+
845}
+
846
+
872std::pair<TER, STAmount>
+
873AMMWithdraw::equalWithdrawLimit(
+
874 Sandbox& view,
+
875 SLE const& ammSle,
+
876 AccountID const& ammAccount,
+
877 STAmount const& amountBalance,
+
878 STAmount const& amount2Balance,
+
879 STAmount const& lptAMMBalance,
+
880 STAmount const& amount,
+
881 STAmount const& amount2,
+
882 std::uint16_t tfee)
+
883{
+
884 auto frac = Number{amount} / amountBalance;
+
885 auto amount2Withdraw =
+
886 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
+
887 auto tokensAdj =
+
888 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
+
889 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
+
890 return {tecAMM_INVALID_TOKENS, STAmount{}};
+
891 // factor in the adjusted tokens
+
892 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
+
893 amount2Withdraw =
+
894 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
+
895 if (amount2Withdraw <= amount2)
+
896 {
+
897 return withdraw(
+
898 view,
+
899 ammSle,
+
900 ammAccount,
+
901 amountBalance,
+
902 amount,
+
903 amount2Withdraw,
+
904 lptAMMBalance,
+
905 tokensAdj,
+
906 tfee);
+
907 }
+
908
+
909 frac = Number{amount2} / amount2Balance;
+
910 auto amountWithdraw =
+
911 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
+
912 tokensAdj =
+
913 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
+
914 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
+
915 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
+
916 // factor in the adjusted tokens
+
917 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
+
918 amountWithdraw =
+
919 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
+
920 if (!view.rules().enabled(fixAMMv1_3))
+
921 {
+
922 // LCOV_EXCL_START
+
923 XRPL_ASSERT(
+
924 amountWithdraw <= amount,
+
925 "ripple::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
+
926 // LCOV_EXCL_STOP
+
927 }
+
928 else if (amountWithdraw > amount)
+
929 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
+
930 return withdraw(
+
931 view,
+
932 ammSle,
+
933 ammAccount,
+
934 amountBalance,
+
935 amountWithdraw,
+
936 amount2,
+
937 lptAMMBalance,
+
938 tokensAdj,
+
939 tfee);
+
940}
+
941
+
947std::pair<TER, STAmount>
+
948AMMWithdraw::singleWithdraw(
+
949 Sandbox& view,
+
950 SLE const& ammSle,
+
951 AccountID const& ammAccount,
+
952 STAmount const& amountBalance,
+
953 STAmount const& lptAMMBalance,
+
954 STAmount const& amount,
+
955 std::uint16_t tfee)
+
956{
+
957 auto const tokens = adjustLPTokensIn(
+
958 view.rules(),
+
959 lptAMMBalance,
+
960 lpTokensIn(amountBalance, amount, lptAMMBalance, tfee),
+
961 isWithdrawAll(ctx_.tx));
+
962 if (tokens == beast::zero)
+
963 {
+
964 if (!view.rules().enabled(fixAMMv1_3))
+
965 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
+
966 else
+
967 return {tecAMM_INVALID_TOKENS, STAmount{}};
+
968 }
+
969 // factor in the adjusted tokens
+
970 auto const [tokensAdj, amountWithdrawAdj] = adjustAssetOutByTokens(
+
971 view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
+
972 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
+
973 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
+
974 return withdraw(
+
975 view,
+
976 ammSle,
+
977 ammAccount,
+
978 amountBalance,
+
979 amountWithdrawAdj,
+
980 std::nullopt,
+
981 lptAMMBalance,
+
982 tokensAdj,
+
983 tfee);
+
984}
+
985
+
996std::pair<TER, STAmount>
+
997AMMWithdraw::singleWithdrawTokens(
+
998 Sandbox& view,
+
999 SLE const& ammSle,
+
1000 AccountID const& ammAccount,
+
1001 STAmount const& amountBalance,
+
1002 STAmount const& lptAMMBalance,
+
1003 STAmount const& amount,
+
1004 STAmount const& lpTokensWithdraw,
+
1005 std::uint16_t tfee)
+
1006{
+
1007 auto const tokensAdj = adjustLPTokensIn(
+
1008 view.rules(), lptAMMBalance, lpTokensWithdraw, isWithdrawAll(ctx_.tx));
+
1009 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
+
1010 return {tecAMM_INVALID_TOKENS, STAmount{}};
+
1011 // the adjusted tokens are factored in
+
1012 auto const amountWithdraw =
+
1013 ammAssetOut(amountBalance, lptAMMBalance, tokensAdj, tfee);
+
1014 if (amount == beast::zero || amountWithdraw >= amount)
+
1015 {
+
1016 return withdraw(
+
1017 view,
+
1018 ammSle,
+
1019 ammAccount,
+
1020 amountBalance,
+
1021 amountWithdraw,
+
1022 std::nullopt,
+
1023 lptAMMBalance,
+
1024 tokensAdj,
+
1025 tfee);
+
1026 }
+
1027
+
1028 return {tecAMM_FAILED, STAmount{}};
+
1029}
+
1030
+
1050std::pair<TER, STAmount>
+
1051AMMWithdraw::singleWithdrawEPrice(
+
1052 Sandbox& view,
+
1053 SLE const& ammSle,
+
1054 AccountID const& ammAccount,
+
1055 STAmount const& amountBalance,
+
1056 STAmount const& lptAMMBalance,
+
1057 STAmount const& amount,
+
1058 STAmount const& ePrice,
+
1059 std::uint16_t tfee)
+
1060{
+
1061 // LPTokens is asset in => E = t / a and formula (8) is:
+
1062 // a = A*(t1**2 + t1*(f - 2))/(t1*f - 1)
+
1063 // substitute a as t/E =>
+
1064 // t/E = A*(t1**2 + t1*(f - 2))/(t1*f - 1), t1=t/T => t = t1*T
+
1065 // t1*T/E = A*((t/T)**2 + t*(f - 2)/T)/(t*f/T - 1) =>
+
1066 // T/E = A*(t1 + f-2)/(t1*f - 1) =>
+
1067 // T*(t1*f - 1) = A*E*(t1 + f - 2) =>
+
1068 // t1*T*f - T = t1*A*E + A*E*(f - 2) =>
+
1069 // t1*(T*f - A*E) = T + A*E*(f - 2) =>
+
1070 // t = T*(T + A*E*(f - 2))/(T*f - A*E)
+
1071 Number const ae = amountBalance * ePrice;
+
1072 auto const f = getFee(tfee);
+
1073 auto tokNoRoundCb = [&] {
+
1074 return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) /
+
1075 (lptAMMBalance * f - ae);
+
1076 };
+
1077 auto tokProdCb = [&] {
+
1078 return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae);
+
1079 };
+
1080 auto const tokensAdj = getRoundedLPTokens(
+
1081 view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No);
+
1082 if (tokensAdj <= beast::zero)
+
1083 {
+
1084 if (!view.rules().enabled(fixAMMv1_3))
+
1085 return {tecAMM_FAILED, STAmount{}};
+
1086 else
+
1087 return {tecAMM_INVALID_TOKENS, STAmount{}};
+
1088 }
+
1089 auto amtNoRoundCb = [&] { return tokensAdj / ePrice; };
+
1090 auto amtProdCb = [&] { return tokensAdj / ePrice; };
+
1091 // the adjusted tokens are factored in
+
1092 auto const amountWithdraw = getRoundedAsset(
+
1093 view.rules(), amtNoRoundCb, amount, amtProdCb, IsDeposit::No);
+
1094 if (amount == beast::zero || amountWithdraw >= amount)
+
1095 {
+
1096 return withdraw(
+
1097 view,
+
1098 ammSle,
+
1099 ammAccount,
+
1100 amountBalance,
+
1101 amountWithdraw,
+
1102 std::nullopt,
+
1103 lptAMMBalance,
+
1104 tokensAdj,
+
1105 tfee);
+
1106 }
+
1107
+
1108 return {tecAMM_FAILED, STAmount{}};
+
1109}
+
1110
+
1111WithdrawAll
+
1112AMMWithdraw::isWithdrawAll(STTx const& tx)
+
1113{
+
1114 if (tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll))
+
1115 return WithdrawAll::Yes;
+
1116 return WithdrawAll::No;
+
1117}
+
1118} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:60
Stream error() const
Definition: Journal.h:346
Stream debug() const
Definition: Journal.h:328
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
-
WithdrawAll isWithdrawAll(STTx const &tx)
Check from the flags if it's withdraw all.
-
std::pair< TER, STAmount > singleWithdrawEPrice(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &ePrice, std::uint16_t tfee)
Withdraw single asset (Asset1Out, EPrice) with two constraints.
-
std::pair< TER, STAmount > equalWithdrawLimit(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &amount2, std::uint16_t tfee)
Withdraw both assets (Asset1Out, Asset2Out) with the constraints on the maximum amount of each asset ...
-
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > equalWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const account, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHanding, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Equal-asset withdrawal (LPTokens) of some AMM instance pools shares represented by the number of LPTo...
+
static WithdrawAll isWithdrawAll(STTx const &tx)
Check from the flags if it's withdraw all.
+
std::pair< TER, STAmount > singleWithdrawEPrice(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &ePrice, std::uint16_t tfee)
Withdraw single asset (Asset1Out, EPrice) with two constraints.
+
std::pair< TER, STAmount > equalWithdrawLimit(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &amount2, std::uint16_t tfee)
Withdraw both assets (Asset1Out, Asset2Out) with the constraints on the maximum amount of each asset ...
+
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > equalWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const account, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHanding, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Equal-asset withdrawal (LPTokens) of some AMM instance pools shares represented by the number of LPTo...
std::pair< TER, bool > applyGuts(Sandbox &view)
-
std::pair< TER, STAmount > singleWithdraw(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, std::uint16_t tfee)
Single asset withdrawal (Asset1Out) equivalent to the amount specified in Asset1Out.
-
static std::pair< TER, bool > deleteAMMAccountIfEmpty(Sandbox &sb, std::shared_ptr< SLE > const ammSle, STAmount const &lpTokenBalance, Issue const &issue1, Issue const &issue2, beast::Journal const &journal)
+
std::pair< TER, STAmount > singleWithdraw(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, std::uint16_t tfee)
Single asset withdrawal (Asset1Out) equivalent to the amount specified in Asset1Out.
+
static std::pair< TER, bool > deleteAMMAccountIfEmpty(Sandbox &sb, std::shared_ptr< SLE > const ammSle, STAmount const &lpTokenBalance, Issue const &issue1, Issue const &issue2, beast::Journal const &journal)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMWithdraw.cpp:32
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > withdraw(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, AccountID const &account, STAmount const &amountBalance, STAmount const &amountWithdraw, std::optional< STAmount > const &amount2Withdraw, STAmount const &lpTokensAMMBalance, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHandling, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Withdraw requested assets and token from AMM into LP account.
TER doApply() override
-
std::pair< TER, STAmount > singleWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &lpTokensWithdraw, std::uint16_t tfee)
Single asset withdrawal (Asset1Out, LPTokens) proportional to the share specified by tokens.
+
std::pair< TER, STAmount > singleWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &lpTokensWithdraw, std::uint16_t tfee)
Single asset withdrawal (Asset1Out, LPTokens) proportional to the share specified by tokens.
RawView & rawView()
Definition: ApplyContext.h:91
ApplyView & view()
Definition: ApplyContext.h:78
beast::Journal const journal
Definition: ApplyContext.h:75
@@ -1080,6 +1157,7 @@ $(function() {
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
virtual Rules const & rules() const =0
Returns the tx processing rules.
+
Rules controlling protocol behavior.
Definition: Rules.h:38
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
Definition: STAmount.h:50
Issue const & issue() const
Definition: STAmount.h:496
@@ -1126,23 +1204,25 @@ $(function() {
constexpr std::uint32_t tfWithdrawMask
Definition: TxFlags.h:228
TER deleteAMMAccount(Sandbox &view, Issue const &asset, Issue const &asset2, beast::Journal j)
Delete trustlines to AMM.
Definition: AMMUtils.cpp:282
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account)
Check if the account lacks required authorization.
Definition: View.cpp:2288
+
std::pair< STAmount, STAmount > adjustAssetOutByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
Definition: AMMHelpers.cpp:375
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition: View.cpp:2172
-
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
-
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
Definition: AMMHelpers.cpp:90
constexpr std::uint32_t tfLimitLPToken
Definition: TxFlags.h:220
-
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:53
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition: AMMCore.cpp:129
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:91
-
std::tuple< STAmount, std::optional< STAmount >, STAmount > adjustAmountsByLPTokens(STAmount const &amountBalance, STAmount const &amount, std::optional< STAmount > const &amount2, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee, bool isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
Definition: AMMHelpers.cpp:147
constexpr std::uint32_t tfOneAssetLPToken
Definition: TxFlags.h:219
Expected< bool, TER > isOnlyLiquidityProvider(ReadView const &view, Issue const &ammIssue, AccountID const &lpAccount)
Return true if the Liquidity Provider is the only AMM provider, false otherwise.
Definition: AMMUtils.cpp:386
+
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
Definition: AMMHelpers.cpp:311
static std::optional< STAmount > tokensWithdraw(STAmount const &lpTokens, std::optional< STAmount > const &tokensIn, std::uint32_t flags)
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition: View.cpp:249
+
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
Definition: AMMHelpers.cpp:173
constexpr std::uint32_t tfTwoAsset
Definition: TxFlags.h:218
STAmount ammLPHolds(ReadView const &view, Currency const &cur1, Currency const &cur2, AccountID const &ammAccount, AccountID const &lpAccount, beast::Journal const j)
Get the balance of LP tokens.
Definition: AMMUtils.cpp:112
+
std::tuple< STAmount, std::optional< STAmount >, STAmount > adjustAmountsByLPTokens(STAmount const &amountBalance, STAmount const &amount, std::optional< STAmount > const &amount2, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee, IsDeposit isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
Definition: AMMHelpers.cpp:187
constexpr std::uint32_t tfWithdrawAll
Definition: TxFlags.h:215
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:160
+
static STAmount adjustLPTokensIn(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &lpTokensWithdraw, WithdrawAll withdrawAll)
@ Yes
+
STAmount ammAssetOut(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
Definition: AMMHelpers.cpp:145
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition: Issue.h:126
@ tecINCOMPLETE
Definition: TER.h:335
@ tecFROZEN
Definition: TER.h:303
@@ -1154,16 +1234,19 @@ $(function() {
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:307
constexpr std::uint32_t tfLPToken
Definition: TxFlags.h:214
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition: AMMCore.h:101
+
@ No
@ tesSUCCESS
Definition: TER.h:244
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition: AMMCore.cpp:80
bool isTesSuccess(TER x) noexcept
Definition: TER.h:672
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 AMM pool and LP token balances.
Definition: AMMUtils.cpp:46
constexpr std::uint32_t tfWithdrawSubTx
Definition: TxFlags.h:222
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
+
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
Definition: AMMHelpers.cpp:112
@ terNO_AMM
Definition: TER.h:227
TERSubset< CanCvtToTER > TER
Definition: TER.h:643
-
STAmount withdrawByTokens(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
Definition: AMMHelpers.cpp:114
-
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition: AMMHelpers.h:127
+
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition: AMMHelpers.h:678
+
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition: AMMHelpers.h:129
+
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
Definition: AMMHelpers.cpp:401
TER accountSend(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Calls static accountSendIOU if saAmount represents Issue.
Definition: View.cpp:1998
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:603
@ temMALFORMED
Definition: TER.h:87
diff --git a/AMMWithdraw_8h_source.html b/AMMWithdraw_8h_source.html index 8f04bdcf42..1bed6df632 100644 --- a/AMMWithdraw_8h_source.html +++ b/AMMWithdraw_8h_source.html @@ -105,7 +105,7 @@ $(function() {
27
28class Sandbox;
29
-
67enum class WithdrawAll : bool { No = false, Yes };
+
67enum class WithdrawAll : bool { No = false, Yes };
68
69class AMMWithdraw : public Transactor
70{
@@ -240,7 +240,7 @@ $(function() {
300 STAmount const& ePrice,
301 std::uint16_t tfee);
302
-
304 WithdrawAll
+
304 static WithdrawAll
305 isWithdrawAll(STTx const& tx);
306};
307
@@ -249,20 +249,20 @@ $(function() {
310#endif // RIPPLE_TX_AMMWITHDRAW_H_INCLUDED
A generic endpoint for log messages.
Definition: Journal.h:60
Definition: AMMWithdraw.h:70
-
WithdrawAll isWithdrawAll(STTx const &tx)
Check from the flags if it's withdraw all.
+
static WithdrawAll isWithdrawAll(STTx const &tx)
Check from the flags if it's withdraw all.
AMMWithdraw(ApplyContext &ctx)
Definition: AMMWithdraw.h:74
-
std::pair< TER, STAmount > singleWithdrawEPrice(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &ePrice, std::uint16_t tfee)
Withdraw single asset (Asset1Out, EPrice) with two constraints.
-
std::pair< TER, STAmount > equalWithdrawLimit(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &amount2, std::uint16_t tfee)
Withdraw both assets (Asset1Out, Asset2Out) with the constraints on the maximum amount of each asset ...
-
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > equalWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const account, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHanding, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Equal-asset withdrawal (LPTokens) of some AMM instance pools shares represented by the number of LPTo...
+
std::pair< TER, STAmount > singleWithdrawEPrice(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &ePrice, std::uint16_t tfee)
Withdraw single asset (Asset1Out, EPrice) with two constraints.
+
std::pair< TER, STAmount > equalWithdrawLimit(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &amount2, std::uint16_t tfee)
Withdraw both assets (Asset1Out, Asset2Out) with the constraints on the maximum amount of each asset ...
+
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > equalWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const account, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHanding, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Equal-asset withdrawal (LPTokens) of some AMM instance pools shares represented by the number of LPTo...
std::pair< TER, bool > applyGuts(Sandbox &view)
-
std::pair< TER, STAmount > singleWithdraw(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, std::uint16_t tfee)
Single asset withdrawal (Asset1Out) equivalent to the amount specified in Asset1Out.
+
std::pair< TER, STAmount > singleWithdraw(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, std::uint16_t tfee)
Single asset withdrawal (Asset1Out) equivalent to the amount specified in Asset1Out.
static constexpr ConsequencesFactoryType ConsequencesFactory
Definition: AMMWithdraw.h:72
-
static std::pair< TER, bool > deleteAMMAccountIfEmpty(Sandbox &sb, std::shared_ptr< SLE > const ammSle, STAmount const &lpTokenBalance, Issue const &issue1, Issue const &issue2, beast::Journal const &journal)
+
static std::pair< TER, bool > deleteAMMAccountIfEmpty(Sandbox &sb, std::shared_ptr< SLE > const ammSle, STAmount const &lpTokenBalance, Issue const &issue1, Issue const &issue2, beast::Journal const &journal)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMWithdraw.cpp:32
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > withdraw(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, AccountID const &account, STAmount const &amountBalance, STAmount const &amountWithdraw, std::optional< STAmount > const &amount2Withdraw, STAmount const &lpTokensAMMBalance, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHandling, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Withdraw requested assets and token from AMM into LP account.
TER doApply() override
-
std::pair< TER, STAmount > singleWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &lpTokensWithdraw, std::uint16_t tfee)
Single asset withdrawal (Asset1Out, LPTokens) proportional to the share specified by tokens.
+
std::pair< TER, STAmount > singleWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &lpTokensWithdraw, std::uint16_t tfee)
Single asset withdrawal (Asset1Out, LPTokens) proportional to the share specified by tokens.
State information when applying a tx.
Definition: ApplyContext.h:37
A currency issued by an account.
Definition: Issue.h:36
Definition: STAmount.h:50
@@ -279,9 +279,9 @@ $(function() {
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
WithdrawAll
AMMWithdraw implements AMM withdraw Transactor.
Definition: AMMWithdraw.h:67
-
@ Yes
-
@ No
FreezeHandling
Controls the treatment of frozen account balances.
Definition: View.h:78
+
@ Yes
+
@ No
diff --git a/AMM_8cpp_source.html b/AMM_8cpp_source.html index f6e2dd07fd..9c590512a6 100644 --- a/AMM_8cpp_source.html +++ b/AMM_8cpp_source.html @@ -98,833 +98,838 @@ $(function() {
20#include <test/jtx/AMM.h>
21#include <test/jtx/Env.h>
22
-
23#include <xrpld/app/misc/AMMUtils.h>
-
24#include <xrpld/rpc/detail/RPCHelpers.h>
-
25
-
26#include <xrpl/protocol/AMMCore.h>
-
27#include <xrpl/protocol/AmountConversions.h>
-
28#include <xrpl/protocol/jss.h>
-
29
-
30namespace ripple {
-
31namespace test {
-
32namespace jtx {
-
33
-
34static Number
-
35number(STAmount const& a)
-
36{
-
37 if (isXRP(a))
-
38 return a.xrp();
-
39 return a;
-
40}
-
41
-
42static IOUAmount
-
43initialTokens(STAmount const& asset1, STAmount const& asset2)
-
44{
-
45 auto const product = number(asset1) * number(asset2);
-
46 return (IOUAmount)(product.mantissa() >= 0 ? root2(product)
-
47 : root2(-product));
-
48}
-
49
-
50AMM::AMM(
-
51 Env& env,
-
52 Account const& account,
-
53 STAmount const& asset1,
-
54 STAmount const& asset2,
-
55 bool log,
-
56 std::uint16_t tfee,
-
57 std::uint32_t fee,
-
58 std::optional<std::uint32_t> flags,
-
59 std::optional<jtx::seq> seq,
-
60 std::optional<jtx::msig> ms,
-
61 std::optional<ter> const& ter,
-
62 bool close)
-
63 : env_(env)
-
64 , creatorAccount_(account)
-
65 , asset1_(asset1)
-
66 , asset2_(asset2)
-
67 , ammID_(keylet::amm(asset1_.issue(), asset2_.issue()).key)
-
68 , initialLPTokens_(initialTokens(asset1, asset2))
-
69 , log_(log)
-
70 , doClose_(close)
-
71 , lastPurchasePrice_(0)
-
72 , bidMin_()
-
73 , bidMax_()
-
74 , msig_(ms)
-
75 , fee_(fee)
-
76 , ammAccount_(create(tfee, flags, seq, ter))
-
77 , lptIssue_(ripple::ammLPTIssue(
-
78 asset1_.issue().currency,
-
79 asset2_.issue().currency,
-
80 ammAccount_))
-
81{
-
82}
-
83
-
84AMM::AMM(
-
85 Env& env,
-
86 Account const& account,
-
87 STAmount const& asset1,
-
88 STAmount const& asset2,
-
89 ter const& ter,
-
90 bool log,
-
91 bool close)
-
92 : AMM(env,
-
93 account,
-
94 asset1,
-
95 asset2,
-
96 log,
-
97 0,
-
98 0,
-
99 std::nullopt,
-
100 std::nullopt,
-
101 std::nullopt,
-
102 ter,
-
103 close)
-
104{
-
105}
-
106
-
107AMM::AMM(
-
108 Env& env,
-
109 Account const& account,
-
110 STAmount const& asset1,
-
111 STAmount const& asset2,
-
112 CreateArg const& arg)
-
113 : AMM(env,
-
114 account,
-
115 asset1,
-
116 asset2,
-
117 arg.log,
-
118 arg.tfee,
-
119 arg.fee,
-
120 arg.flags,
-
121 arg.seq,
-
122 arg.ms,
-
123 arg.err,
-
124 arg.close)
-
125{
-
126}
-
127
-
128[[nodiscard]] AccountID
-
129AMM::create(
-
130 std::uint32_t tfee,
-
131 std::optional<std::uint32_t> const& flags,
-
132 std::optional<jtx::seq> const& seq,
-
133 std::optional<ter> const& ter)
-
134{
-
135 Json::Value jv;
-
136 jv[jss::Account] = creatorAccount_.human();
-
137 jv[jss::Amount] = asset1_.getJson(JsonOptions::none);
-
138 jv[jss::Amount2] = asset2_.getJson(JsonOptions::none);
-
139 jv[jss::TradingFee] = tfee;
-
140 jv[jss::TransactionType] = jss::AMMCreate;
-
141 if (flags)
-
142 jv[jss::Flags] = *flags;
-
143 if (fee_ != 0)
-
144 jv[sfFee] = std::to_string(fee_);
-
145 else
-
146 jv[jss::Fee] = std::to_string(env_.current()->fees().increment.drops());
-
147 submit(jv, seq, ter);
-
148
-
149 if (!ter || env_.ter() == tesSUCCESS)
-
150 {
-
151 if (auto const amm = env_.current()->read(
-
152 keylet::amm(asset1_.issue(), asset2_.issue())))
-
153 {
-
154 return amm->getAccountID(sfAccount);
-
155 }
-
156 }
-
157 return {};
-
158}
-
159
-
160Json::Value
-
161AMM::ammRpcInfo(
-
162 std::optional<AccountID> const& account,
-
163 std::optional<std::string> const& ledgerIndex,
-
164 std::optional<Issue> issue1,
-
165 std::optional<Issue> issue2,
-
166 std::optional<AccountID> const& ammAccount,
-
167 bool ignoreParams,
-
168 unsigned apiVersion) const
-
169{
-
170 Json::Value jv;
-
171 if (account)
-
172 jv[jss::account] = to_string(*account);
-
173 if (ledgerIndex)
-
174 jv[jss::ledger_index] = *ledgerIndex;
-
175 if (!ignoreParams)
-
176 {
-
177 if (issue1 || issue2)
-
178 {
-
179 if (issue1)
-
180 jv[jss::asset] =
-
181 STIssue(sfAsset, *issue1).getJson(JsonOptions::none);
-
182 if (issue2)
-
183 jv[jss::asset2] =
-
184 STIssue(sfAsset2, *issue2).getJson(JsonOptions::none);
-
185 }
-
186 else if (!ammAccount)
-
187 {
-
188 jv[jss::asset] =
-
189 STIssue(sfAsset, asset1_.issue()).getJson(JsonOptions::none);
-
190 jv[jss::asset2] =
-
191 STIssue(sfAsset2, asset2_.issue()).getJson(JsonOptions::none);
-
192 }
-
193 if (ammAccount)
-
194 jv[jss::amm_account] = to_string(*ammAccount);
-
195 }
-
196 auto jr =
-
197 (apiVersion == RPC::apiInvalidVersion
-
198 ? env_.rpc("json", "amm_info", to_string(jv))
-
199 : env_.rpc(apiVersion, "json", "amm_info", to_string(jv)));
-
200 if (jr.isObject() && jr.isMember(jss::result) &&
-
201 jr[jss::result].isMember(jss::status))
-
202 return jr[jss::result];
-
203 return Json::nullValue;
-
204}
-
205
-
206std::tuple<STAmount, STAmount, STAmount>
-
207AMM::balances(
-
208 Issue const& issue1,
-
209 Issue const& issue2,
-
210 std::optional<AccountID> const& account) const
-
211{
-
212 if (auto const amm =
-
213 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())))
-
214 {
-
215 auto const ammAccountID = amm->getAccountID(sfAccount);
-
216 auto const [asset1Balance, asset2Balance] = ammPoolHolds(
-
217 *env_.current(),
-
218 ammAccountID,
-
219 issue1,
-
220 issue2,
-
221 FreezeHandling::fhIGNORE_FREEZE,
-
222 env_.journal);
-
223 auto const lptAMMBalance = account
-
224 ? ammLPHolds(*env_.current(), *amm, *account, env_.journal)
-
225 : amm->getFieldAmount(sfLPTokenBalance);
-
226 return {asset1Balance, asset2Balance, lptAMMBalance};
-
227 }
-
228 return {STAmount{}, STAmount{}, STAmount{}};
-
229}
-
230
-
231bool
-
232AMM::expectBalances(
-
233 STAmount const& asset1,
-
234 STAmount const& asset2,
-
235 IOUAmount const& lpt,
-
236 std::optional<AccountID> const& account) const
-
237{
-
238 auto const [asset1Balance, asset2Balance, lptAMMBalance] =
-
239 balances(asset1.issue(), asset2.issue(), account);
-
240 return asset1 == asset1Balance && asset2 == asset2Balance &&
-
241 lptAMMBalance == STAmount{lpt, lptIssue_};
-
242}
-
243
-
244IOUAmount
-
245AMM::getLPTokensBalance(std::optional<AccountID> const& account) const
-
246{
-
247 if (account)
-
248 return accountHolds(
-
249 *env_.current(),
-
250 *account,
-
251 lptIssue_,
-
252 FreezeHandling::fhZERO_IF_FROZEN,
-
253 env_.journal)
-
254 .iou();
-
255 if (auto const amm =
-
256 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())))
-
257 return amm->getFieldAmount(sfLPTokenBalance).iou();
-
258 return IOUAmount{0};
-
259}
-
260
-
261bool
-
262AMM::expectLPTokens(AccountID const& account, IOUAmount const& expTokens) const
-
263{
-
264 if (auto const amm =
-
265 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())))
-
266 {
-
267 auto const lptAMMBalance =
-
268 ammLPHolds(*env_.current(), *amm, account, env_.journal);
-
269 return lptAMMBalance == STAmount{expTokens, lptIssue_};
-
270 }
-
271 return false;
-
272}
-
273
-
274bool
-
275AMM::expectAuctionSlot(
-
276 std::uint32_t fee,
-
277 std::optional<std::uint8_t> timeSlot,
-
278 IOUAmount expectedPrice) const
-
279{
-
280 return expectAuctionSlot([&](std::uint32_t slotFee,
-
281 std::optional<std::uint8_t> slotInterval,
-
282 IOUAmount const& slotPrice,
-
283 auto const&) {
-
284 return slotFee == fee &&
-
285 // Auction slot might be expired, in which case slotInterval is
-
286 // 0
-
287 ((!timeSlot && slotInterval == 0) || slotInterval == timeSlot) &&
-
288 slotPrice == expectedPrice;
-
289 });
-
290}
-
291
-
292bool
-
293AMM::expectAuctionSlot(std::vector<AccountID> const& authAccounts) const
-
294{
-
295 return expectAuctionSlot([&](std::uint32_t,
-
296 std::optional<std::uint8_t>,
-
297 IOUAmount const&,
-
298 STArray const& accounts) {
-
299 for (auto const& account : accounts)
-
300 {
-
301 if (std::find(
-
302 authAccounts.cbegin(),
-
303 authAccounts.cend(),
-
304 account.getAccountID(sfAccount)) == authAccounts.end())
-
305 return false;
-
306 }
-
307 return true;
-
308 });
-
309}
-
310
-
311bool
-
312AMM::expectTradingFee(std::uint16_t fee) const
-
313{
-
314 auto const amm =
-
315 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue()));
-
316 return amm && (*amm)[sfTradingFee] == fee;
-
317}
-
318
-
319bool
-
320AMM::ammExists() const
-
321{
-
322 return env_.current()->read(keylet::account(ammAccount_)) != nullptr &&
-
323 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())) !=
-
324 nullptr;
-
325}
-
326
-
327bool
-
328AMM::expectAmmRpcInfo(
-
329 STAmount const& asset1,
-
330 STAmount const& asset2,
-
331 IOUAmount const& balance,
-
332 std::optional<AccountID> const& account,
-
333 std::optional<std::string> const& ledger_index,
-
334 std::optional<AccountID> const& ammAccount) const
-
335{
-
336 auto const jv = ammRpcInfo(
-
337 account, ledger_index, std::nullopt, std::nullopt, ammAccount);
-
338 return expectAmmInfo(asset1, asset2, balance, jv);
-
339}
-
340
-
341bool
-
342AMM::expectAmmInfo(
-
343 STAmount const& asset1,
-
344 STAmount const& asset2,
-
345 IOUAmount const& balance,
-
346 Json::Value const& jvres) const
-
347{
-
348 if (!jvres.isMember(jss::amm))
-
349 return false;
-
350 auto const& jv = jvres[jss::amm];
-
351 if (!jv.isMember(jss::amount) || !jv.isMember(jss::amount2) ||
-
352 !jv.isMember(jss::lp_token))
-
353 return false;
-
354 STAmount asset1Info;
-
355 if (!amountFromJsonNoThrow(asset1Info, jv[jss::amount]))
-
356 return false;
-
357 STAmount asset2Info;
-
358 if (!amountFromJsonNoThrow(asset2Info, jv[jss::amount2]))
-
359 return false;
-
360 STAmount lptBalance;
-
361 if (!amountFromJsonNoThrow(lptBalance, jv[jss::lp_token]))
-
362 return false;
-
363 // ammRpcInfo returns unordered assets
-
364 if (asset1Info.issue() != asset1.issue())
-
365 std::swap(asset1Info, asset2Info);
-
366 return asset1 == asset1Info && asset2 == asset2Info &&
-
367 lptBalance == STAmount{balance, lptIssue_};
-
368}
-
369
-
370void
-
371AMM::setTokens(
-
372 Json::Value& jv,
-
373 std::optional<std::pair<Issue, Issue>> const& assets)
-
374{
-
375 if (assets)
-
376 {
-
377 jv[jss::Asset] =
-
378 STIssue(sfAsset, assets->first).getJson(JsonOptions::none);
-
379 jv[jss::Asset2] =
-
380 STIssue(sfAsset, assets->second).getJson(JsonOptions::none);
-
381 }
-
382 else
-
383 {
-
384 jv[jss::Asset] =
-
385 STIssue(sfAsset, asset1_.issue()).getJson(JsonOptions::none);
-
386 jv[jss::Asset2] =
-
387 STIssue(sfAsset, asset2_.issue()).getJson(JsonOptions::none);
-
388 }
-
389}
-
390
-
391IOUAmount
-
392AMM::deposit(
-
393 std::optional<Account> const& account,
-
394 Json::Value& jv,
-
395 std::optional<std::pair<Issue, Issue>> const& assets,
-
396 std::optional<jtx::seq> const& seq,
-
397 std::optional<ter> const& ter)
-
398{
-
399 auto const& acct = account ? *account : creatorAccount_;
-
400 auto const lpTokens = getLPTokensBalance(acct);
-
401 jv[jss::Account] = acct.human();
-
402 setTokens(jv, assets);
-
403 jv[jss::TransactionType] = jss::AMMDeposit;
-
404 if (fee_ != 0)
-
405 jv[jss::Fee] = std::to_string(fee_);
-
406 submit(jv, seq, ter);
-
407 return getLPTokensBalance(acct) - lpTokens;
-
408}
-
409
-
410IOUAmount
-
411AMM::deposit(
-
412 std::optional<Account> const& account,
-
413 LPToken tokens,
-
414 std::optional<STAmount> const& asset1In,
-
415 std::optional<std::uint32_t> const& flags,
-
416 std::optional<ter> const& ter)
-
417{
-
418 return deposit(
-
419 account,
-
420 tokens,
-
421 asset1In,
-
422 std::nullopt,
-
423 std::nullopt,
-
424 flags,
-
425 std::nullopt,
-
426 std::nullopt,
+
23#include <xrpld/app/misc/AMMHelpers.h>
+
24#include <xrpld/app/misc/AMMUtils.h>
+
25#include <xrpld/rpc/detail/RPCHelpers.h>
+
26
+
27#include <xrpl/protocol/AMMCore.h>
+
28#include <xrpl/protocol/AmountConversions.h>
+
29#include <xrpl/protocol/jss.h>
+
30
+
31namespace ripple {
+
32namespace test {
+
33namespace jtx {
+
34
+
35static Number
+
36number(STAmount const& a)
+
37{
+
38 if (isXRP(a))
+
39 return a.xrp();
+
40 return a;
+
41}
+
42
+
43IOUAmount
+
44AMM::initialTokens()
+
45{
+
46 if (!env_.enabled(fixAMMv1_3))
+
47 {
+
48 auto const product = number(asset1_) * number(asset2_);
+
49 return (IOUAmount)(product.mantissa() >= 0 ? root2(product)
+
50 : root2(-product));
+
51 }
+
52 return getLPTokensBalance();
+
53}
+
54
+
55AMM::AMM(
+
56 Env& env,
+
57 Account const& account,
+
58 STAmount const& asset1,
+
59 STAmount const& asset2,
+
60 bool log,
+
61 std::uint16_t tfee,
+
62 std::uint32_t fee,
+
63 std::optional<std::uint32_t> flags,
+
64 std::optional<jtx::seq> seq,
+
65 std::optional<jtx::msig> ms,
+
66 std::optional<ter> const& ter,
+
67 bool close)
+
68 : env_(env)
+
69 , creatorAccount_(account)
+
70 , asset1_(asset1)
+
71 , asset2_(asset2)
+
72 , ammID_(keylet::amm(asset1_.issue(), asset2_.issue()).key)
+
73 , log_(log)
+
74 , doClose_(close)
+
75 , lastPurchasePrice_(0)
+
76 , bidMin_()
+
77 , bidMax_()
+
78 , msig_(ms)
+
79 , fee_(fee)
+
80 , ammAccount_(create(tfee, flags, seq, ter))
+
81 , lptIssue_(ripple::ammLPTIssue(
+
82 asset1_.issue().currency,
+
83 asset2_.issue().currency,
+
84 ammAccount_))
+
85 , initialLPTokens_(initialTokens())
+
86{
+
87}
+
88
+
89AMM::AMM(
+
90 Env& env,
+
91 Account const& account,
+
92 STAmount const& asset1,
+
93 STAmount const& asset2,
+
94 ter const& ter,
+
95 bool log,
+
96 bool close)
+
97 : AMM(env,
+
98 account,
+
99 asset1,
+
100 asset2,
+
101 log,
+
102 0,
+
103 0,
+
104 std::nullopt,
+
105 std::nullopt,
+
106 std::nullopt,
+
107 ter,
+
108 close)
+
109{
+
110}
+
111
+
112AMM::AMM(
+
113 Env& env,
+
114 Account const& account,
+
115 STAmount const& asset1,
+
116 STAmount const& asset2,
+
117 CreateArg const& arg)
+
118 : AMM(env,
+
119 account,
+
120 asset1,
+
121 asset2,
+
122 arg.log,
+
123 arg.tfee,
+
124 arg.fee,
+
125 arg.flags,
+
126 arg.seq,
+
127 arg.ms,
+
128 arg.err,
+
129 arg.close)
+
130{
+
131}
+
132
+
133[[nodiscard]] AccountID
+
134AMM::create(
+
135 std::uint32_t tfee,
+
136 std::optional<std::uint32_t> const& flags,
+
137 std::optional<jtx::seq> const& seq,
+
138 std::optional<ter> const& ter)
+
139{
+
140 Json::Value jv;
+
141 jv[jss::Account] = creatorAccount_.human();
+
142 jv[jss::Amount] = asset1_.getJson(JsonOptions::none);
+
143 jv[jss::Amount2] = asset2_.getJson(JsonOptions::none);
+
144 jv[jss::TradingFee] = tfee;
+
145 jv[jss::TransactionType] = jss::AMMCreate;
+
146 if (flags)
+
147 jv[jss::Flags] = *flags;
+
148 if (fee_ != 0)
+
149 jv[sfFee] = std::to_string(fee_);
+
150 else
+
151 jv[jss::Fee] = std::to_string(env_.current()->fees().increment.drops());
+
152 submit(jv, seq, ter);
+
153
+
154 if (!ter || env_.ter() == tesSUCCESS)
+
155 {
+
156 if (auto const amm = env_.current()->read(
+
157 keylet::amm(asset1_.issue(), asset2_.issue())))
+
158 {
+
159 return amm->getAccountID(sfAccount);
+
160 }
+
161 }
+
162 return {};
+
163}
+
164
+
165Json::Value
+
166AMM::ammRpcInfo(
+
167 std::optional<AccountID> const& account,
+
168 std::optional<std::string> const& ledgerIndex,
+
169 std::optional<Issue> issue1,
+
170 std::optional<Issue> issue2,
+
171 std::optional<AccountID> const& ammAccount,
+
172 bool ignoreParams,
+
173 unsigned apiVersion) const
+
174{
+
175 Json::Value jv;
+
176 if (account)
+
177 jv[jss::account] = to_string(*account);
+
178 if (ledgerIndex)
+
179 jv[jss::ledger_index] = *ledgerIndex;
+
180 if (!ignoreParams)
+
181 {
+
182 if (issue1 || issue2)
+
183 {
+
184 if (issue1)
+
185 jv[jss::asset] =
+
186 STIssue(sfAsset, *issue1).getJson(JsonOptions::none);
+
187 if (issue2)
+
188 jv[jss::asset2] =
+
189 STIssue(sfAsset2, *issue2).getJson(JsonOptions::none);
+
190 }
+
191 else if (!ammAccount)
+
192 {
+
193 jv[jss::asset] =
+
194 STIssue(sfAsset, asset1_.issue()).getJson(JsonOptions::none);
+
195 jv[jss::asset2] =
+
196 STIssue(sfAsset2, asset2_.issue()).getJson(JsonOptions::none);
+
197 }
+
198 if (ammAccount)
+
199 jv[jss::amm_account] = to_string(*ammAccount);
+
200 }
+
201 auto jr =
+
202 (apiVersion == RPC::apiInvalidVersion
+
203 ? env_.rpc("json", "amm_info", to_string(jv))
+
204 : env_.rpc(apiVersion, "json", "amm_info", to_string(jv)));
+
205 if (jr.isObject() && jr.isMember(jss::result) &&
+
206 jr[jss::result].isMember(jss::status))
+
207 return jr[jss::result];
+
208 return Json::nullValue;
+
209}
+
210
+
211std::tuple<STAmount, STAmount, STAmount>
+
212AMM::balances(
+
213 Issue const& issue1,
+
214 Issue const& issue2,
+
215 std::optional<AccountID> const& account) const
+
216{
+
217 if (auto const amm =
+
218 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())))
+
219 {
+
220 auto const ammAccountID = amm->getAccountID(sfAccount);
+
221 auto const [asset1Balance, asset2Balance] = ammPoolHolds(
+
222 *env_.current(),
+
223 ammAccountID,
+
224 issue1,
+
225 issue2,
+
226 FreezeHandling::fhIGNORE_FREEZE,
+
227 env_.journal);
+
228 auto const lptAMMBalance = account
+
229 ? ammLPHolds(*env_.current(), *amm, *account, env_.journal)
+
230 : amm->getFieldAmount(sfLPTokenBalance);
+
231 return {asset1Balance, asset2Balance, lptAMMBalance};
+
232 }
+
233 return {STAmount{}, STAmount{}, STAmount{}};
+
234}
+
235
+
236bool
+
237AMM::expectBalances(
+
238 STAmount const& asset1,
+
239 STAmount const& asset2,
+
240 IOUAmount const& lpt,
+
241 std::optional<AccountID> const& account) const
+
242{
+
243 auto const [asset1Balance, asset2Balance, lptAMMBalance] =
+
244 balances(asset1.issue(), asset2.issue(), account);
+
245 return asset1 == asset1Balance && asset2 == asset2Balance &&
+
246 lptAMMBalance == STAmount{lpt, lptIssue_};
+
247}
+
248
+
249IOUAmount
+
250AMM::getLPTokensBalance(std::optional<AccountID> const& account) const
+
251{
+
252 if (account)
+
253 return accountHolds(
+
254 *env_.current(),
+
255 *account,
+
256 lptIssue_,
+
257 FreezeHandling::fhZERO_IF_FROZEN,
+
258 env_.journal)
+
259 .iou();
+
260 if (auto const amm =
+
261 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())))
+
262 return amm->getFieldAmount(sfLPTokenBalance).iou();
+
263 return IOUAmount{0};
+
264}
+
265
+
266bool
+
267AMM::expectLPTokens(AccountID const& account, IOUAmount const& expTokens) const
+
268{
+
269 if (auto const amm =
+
270 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())))
+
271 {
+
272 auto const lptAMMBalance =
+
273 ammLPHolds(*env_.current(), *amm, account, env_.journal);
+
274 return lptAMMBalance == STAmount{expTokens, lptIssue_};
+
275 }
+
276 return false;
+
277}
+
278
+
279bool
+
280AMM::expectAuctionSlot(
+
281 std::uint32_t fee,
+
282 std::optional<std::uint8_t> timeSlot,
+
283 IOUAmount expectedPrice) const
+
284{
+
285 return expectAuctionSlot([&](std::uint32_t slotFee,
+
286 std::optional<std::uint8_t> slotInterval,
+
287 IOUAmount const& slotPrice,
+
288 auto const&) {
+
289 return slotFee == fee &&
+
290 // Auction slot might be expired, in which case slotInterval is
+
291 // 0
+
292 ((!timeSlot && slotInterval == 0) || slotInterval == timeSlot) &&
+
293 slotPrice == expectedPrice;
+
294 });
+
295}
+
296
+
297bool
+
298AMM::expectAuctionSlot(std::vector<AccountID> const& authAccounts) const
+
299{
+
300 return expectAuctionSlot([&](std::uint32_t,
+
301 std::optional<std::uint8_t>,
+
302 IOUAmount const&,
+
303 STArray const& accounts) {
+
304 for (auto const& account : accounts)
+
305 {
+
306 if (std::find(
+
307 authAccounts.cbegin(),
+
308 authAccounts.cend(),
+
309 account.getAccountID(sfAccount)) == authAccounts.end())
+
310 return false;
+
311 }
+
312 return true;
+
313 });
+
314}
+
315
+
316bool
+
317AMM::expectTradingFee(std::uint16_t fee) const
+
318{
+
319 auto const amm =
+
320 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue()));
+
321 return amm && (*amm)[sfTradingFee] == fee;
+
322}
+
323
+
324bool
+
325AMM::ammExists() const
+
326{
+
327 return env_.current()->read(keylet::account(ammAccount_)) != nullptr &&
+
328 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())) !=
+
329 nullptr;
+
330}
+
331
+
332bool
+
333AMM::expectAmmRpcInfo(
+
334 STAmount const& asset1,
+
335 STAmount const& asset2,
+
336 IOUAmount const& balance,
+
337 std::optional<AccountID> const& account,
+
338 std::optional<std::string> const& ledger_index,
+
339 std::optional<AccountID> const& ammAccount) const
+
340{
+
341 auto const jv = ammRpcInfo(
+
342 account, ledger_index, std::nullopt, std::nullopt, ammAccount);
+
343 return expectAmmInfo(asset1, asset2, balance, jv);
+
344}
+
345
+
346bool
+
347AMM::expectAmmInfo(
+
348 STAmount const& asset1,
+
349 STAmount const& asset2,
+
350 IOUAmount const& balance,
+
351 Json::Value const& jvres) const
+
352{
+
353 if (!jvres.isMember(jss::amm))
+
354 return false;
+
355 auto const& jv = jvres[jss::amm];
+
356 if (!jv.isMember(jss::amount) || !jv.isMember(jss::amount2) ||
+
357 !jv.isMember(jss::lp_token))
+
358 return false;
+
359 STAmount asset1Info;
+
360 if (!amountFromJsonNoThrow(asset1Info, jv[jss::amount]))
+
361 return false;
+
362 STAmount asset2Info;
+
363 if (!amountFromJsonNoThrow(asset2Info, jv[jss::amount2]))
+
364 return false;
+
365 STAmount lptBalance;
+
366 if (!amountFromJsonNoThrow(lptBalance, jv[jss::lp_token]))
+
367 return false;
+
368 // ammRpcInfo returns unordered assets
+
369 if (asset1Info.issue() != asset1.issue())
+
370 std::swap(asset1Info, asset2Info);
+
371 return asset1 == asset1Info && asset2 == asset2Info &&
+
372 lptBalance == STAmount{balance, lptIssue_};
+
373}
+
374
+
375void
+
376AMM::setTokens(
+
377 Json::Value& jv,
+
378 std::optional<std::pair<Issue, Issue>> const& assets)
+
379{
+
380 if (assets)
+
381 {
+
382 jv[jss::Asset] =
+
383 STIssue(sfAsset, assets->first).getJson(JsonOptions::none);
+
384 jv[jss::Asset2] =
+
385 STIssue(sfAsset, assets->second).getJson(JsonOptions::none);
+
386 }
+
387 else
+
388 {
+
389 jv[jss::Asset] =
+
390 STIssue(sfAsset, asset1_.issue()).getJson(JsonOptions::none);
+
391 jv[jss::Asset2] =
+
392 STIssue(sfAsset, asset2_.issue()).getJson(JsonOptions::none);
+
393 }
+
394}
+
395
+
396IOUAmount
+
397AMM::deposit(
+
398 std::optional<Account> const& account,
+
399 Json::Value& jv,
+
400 std::optional<std::pair<Issue, Issue>> const& assets,
+
401 std::optional<jtx::seq> const& seq,
+
402 std::optional<ter> const& ter)
+
403{
+
404 auto const& acct = account ? *account : creatorAccount_;
+
405 auto const lpTokens = getLPTokensBalance(acct);
+
406 jv[jss::Account] = acct.human();
+
407 setTokens(jv, assets);
+
408 jv[jss::TransactionType] = jss::AMMDeposit;
+
409 if (fee_ != 0)
+
410 jv[jss::Fee] = std::to_string(fee_);
+
411 submit(jv, seq, ter);
+
412 return getLPTokensBalance(acct) - lpTokens;
+
413}
+
414
+
415IOUAmount
+
416AMM::deposit(
+
417 std::optional<Account> const& account,
+
418 LPToken tokens,
+
419 std::optional<STAmount> const& asset1In,
+
420 std::optional<std::uint32_t> const& flags,
+
421 std::optional<ter> const& ter)
+
422{
+
423 return deposit(
+
424 account,
+
425 tokens,
+
426 asset1In,
427 std::nullopt,
-
428 ter);
-
429}
-
430
-
431IOUAmount
-
432AMM::deposit(
-
433 std::optional<Account> const& account,
-
434 STAmount const& asset1In,
-
435 std::optional<STAmount> const& asset2In,
-
436 std::optional<STAmount> const& maxEP,
-
437 std::optional<std::uint32_t> const& flags,
-
438 std::optional<ter> const& ter)
-
439{
-
440 assert(!(asset2In && maxEP));
-
441 return deposit(
-
442 account,
-
443 std::nullopt,
-
444 asset1In,
-
445 asset2In,
-
446 maxEP,
-
447 flags,
+
428 std::nullopt,
+
429 flags,
+
430 std::nullopt,
+
431 std::nullopt,
+
432 std::nullopt,
+
433 ter);
+
434}
+
435
+
436IOUAmount
+
437AMM::deposit(
+
438 std::optional<Account> const& account,
+
439 STAmount const& asset1In,
+
440 std::optional<STAmount> const& asset2In,
+
441 std::optional<STAmount> const& maxEP,
+
442 std::optional<std::uint32_t> const& flags,
+
443 std::optional<ter> const& ter)
+
444{
+
445 assert(!(asset2In && maxEP));
+
446 return deposit(
+
447 account,
448 std::nullopt,
-
449 std::nullopt,
-
450 std::nullopt,
-
451 ter);
-
452}
-
453
-
454IOUAmount
-
455AMM::deposit(
-
456 std::optional<Account> const& account,
-
457 std::optional<LPToken> tokens,
-
458 std::optional<STAmount> const& asset1In,
-
459 std::optional<STAmount> const& asset2In,
-
460 std::optional<STAmount> const& maxEP,
-
461 std::optional<std::uint32_t> const& flags,
-
462 std::optional<std::pair<Issue, Issue>> const& assets,
-
463 std::optional<jtx::seq> const& seq,
-
464 std::optional<std::uint16_t> const& tfee,
-
465 std::optional<ter> const& ter)
-
466{
-
467 Json::Value jv;
-
468 if (tokens)
-
469 tokens->tokens(lptIssue_).setJson(jv[jss::LPTokenOut]);
-
470 if (asset1In)
-
471 asset1In->setJson(jv[jss::Amount]);
-
472 if (asset2In)
-
473 asset2In->setJson(jv[jss::Amount2]);
-
474 if (maxEP)
-
475 maxEP->setJson(jv[jss::EPrice]);
-
476 if (tfee)
-
477 jv[jss::TradingFee] = *tfee;
-
478 std::uint32_t jvflags = 0;
-
479 if (flags)
-
480 jvflags = *flags;
-
481 // If including asset1In and asset2In or tokens as
-
482 // deposit min amounts then must set the flags
-
483 // explicitly instead of relying on this logic.
-
484 if (!(jvflags & tfDepositSubTx))
-
485 {
-
486 if (tokens && !asset1In)
-
487 jvflags |= tfLPToken;
-
488 else if (tokens && asset1In)
-
489 jvflags |= tfOneAssetLPToken;
-
490 else if (asset1In && asset2In)
-
491 jvflags |= tfTwoAsset;
-
492 else if (maxEP && asset1In)
-
493 jvflags |= tfLimitLPToken;
-
494 else if (asset1In)
-
495 jvflags |= tfSingleAsset;
-
496 }
-
497 jv[jss::Flags] = jvflags;
-
498 return deposit(account, jv, assets, seq, ter);
-
499}
-
500
-
501IOUAmount
-
502AMM::deposit(DepositArg const& arg)
-
503{
-
504 return deposit(
-
505 arg.account,
-
506 arg.tokens,
-
507 arg.asset1In,
-
508 arg.asset2In,
-
509 arg.maxEP,
-
510 arg.flags,
-
511 arg.assets,
-
512 arg.seq,
-
513 arg.tfee,
-
514 arg.err);
-
515}
-
516
-
517IOUAmount
-
518AMM::withdraw(
-
519 std::optional<Account> const& account,
-
520 Json::Value& jv,
-
521 std::optional<jtx::seq> const& seq,
-
522 std::optional<std::pair<Issue, Issue>> const& assets,
-
523 std::optional<ter> const& ter)
-
524{
-
525 auto const& acct = account ? *account : creatorAccount_;
-
526 auto const lpTokens = getLPTokensBalance(acct);
-
527 jv[jss::Account] = acct.human();
-
528 setTokens(jv, assets);
-
529 jv[jss::TransactionType] = jss::AMMWithdraw;
-
530 if (fee_ != 0)
-
531 jv[jss::Fee] = std::to_string(fee_);
-
532 submit(jv, seq, ter);
-
533 return lpTokens - getLPTokensBalance(acct);
-
534}
-
535
-
536IOUAmount
-
537AMM::withdraw(
-
538 std::optional<Account> const& account,
-
539 std::optional<LPToken> const& tokens,
-
540 std::optional<STAmount> const& asset1Out,
-
541 std::optional<std::uint32_t> const& flags,
-
542 std::optional<ter> const& ter)
-
543{
-
544 return withdraw(
-
545 account,
-
546 tokens,
-
547 asset1Out,
-
548 std::nullopt,
-
549 std::nullopt,
-
550 flags,
-
551 std::nullopt,
-
552 std::nullopt,
-
553 ter);
-
554}
-
555
-
556IOUAmount
-
557AMM::withdraw(
-
558 std::optional<Account> const& account,
-
559 STAmount const& asset1Out,
-
560 std::optional<STAmount> const& asset2Out,
-
561 std::optional<IOUAmount> const& maxEP,
-
562 std::optional<ter> const& ter)
-
563{
-
564 assert(!(asset2Out && maxEP));
-
565 return withdraw(
-
566 account,
-
567 std::nullopt,
-
568 asset1Out,
-
569 asset2Out,
-
570 maxEP,
-
571 std::nullopt,
+
449 asset1In,
+
450 asset2In,
+
451 maxEP,
+
452 flags,
+
453 std::nullopt,
+
454 std::nullopt,
+
455 std::nullopt,
+
456 ter);
+
457}
+
458
+
459IOUAmount
+
460AMM::deposit(
+
461 std::optional<Account> const& account,
+
462 std::optional<LPToken> tokens,
+
463 std::optional<STAmount> const& asset1In,
+
464 std::optional<STAmount> const& asset2In,
+
465 std::optional<STAmount> const& maxEP,
+
466 std::optional<std::uint32_t> const& flags,
+
467 std::optional<std::pair<Issue, Issue>> const& assets,
+
468 std::optional<jtx::seq> const& seq,
+
469 std::optional<std::uint16_t> const& tfee,
+
470 std::optional<ter> const& ter)
+
471{
+
472 Json::Value jv;
+
473 if (tokens)
+
474 tokens->tokens(lptIssue_).setJson(jv[jss::LPTokenOut]);
+
475 if (asset1In)
+
476 asset1In->setJson(jv[jss::Amount]);
+
477 if (asset2In)
+
478 asset2In->setJson(jv[jss::Amount2]);
+
479 if (maxEP)
+
480 maxEP->setJson(jv[jss::EPrice]);
+
481 if (tfee)
+
482 jv[jss::TradingFee] = *tfee;
+
483 std::uint32_t jvflags = 0;
+
484 if (flags)
+
485 jvflags = *flags;
+
486 // If including asset1In and asset2In or tokens as
+
487 // deposit min amounts then must set the flags
+
488 // explicitly instead of relying on this logic.
+
489 if (!(jvflags & tfDepositSubTx))
+
490 {
+
491 if (tokens && !asset1In)
+
492 jvflags |= tfLPToken;
+
493 else if (tokens && asset1In)
+
494 jvflags |= tfOneAssetLPToken;
+
495 else if (asset1In && asset2In)
+
496 jvflags |= tfTwoAsset;
+
497 else if (maxEP && asset1In)
+
498 jvflags |= tfLimitLPToken;
+
499 else if (asset1In)
+
500 jvflags |= tfSingleAsset;
+
501 }
+
502 jv[jss::Flags] = jvflags;
+
503 return deposit(account, jv, assets, seq, ter);
+
504}
+
505
+
506IOUAmount
+
507AMM::deposit(DepositArg const& arg)
+
508{
+
509 return deposit(
+
510 arg.account,
+
511 arg.tokens,
+
512 arg.asset1In,
+
513 arg.asset2In,
+
514 arg.maxEP,
+
515 arg.flags,
+
516 arg.assets,
+
517 arg.seq,
+
518 arg.tfee,
+
519 arg.err);
+
520}
+
521
+
522IOUAmount
+
523AMM::withdraw(
+
524 std::optional<Account> const& account,
+
525 Json::Value& jv,
+
526 std::optional<jtx::seq> const& seq,
+
527 std::optional<std::pair<Issue, Issue>> const& assets,
+
528 std::optional<ter> const& ter)
+
529{
+
530 auto const& acct = account ? *account : creatorAccount_;
+
531 auto const lpTokens = getLPTokensBalance(acct);
+
532 jv[jss::Account] = acct.human();
+
533 setTokens(jv, assets);
+
534 jv[jss::TransactionType] = jss::AMMWithdraw;
+
535 if (fee_ != 0)
+
536 jv[jss::Fee] = std::to_string(fee_);
+
537 submit(jv, seq, ter);
+
538 return lpTokens - getLPTokensBalance(acct);
+
539}
+
540
+
541IOUAmount
+
542AMM::withdraw(
+
543 std::optional<Account> const& account,
+
544 std::optional<LPToken> const& tokens,
+
545 std::optional<STAmount> const& asset1Out,
+
546 std::optional<std::uint32_t> const& flags,
+
547 std::optional<ter> const& ter)
+
548{
+
549 return withdraw(
+
550 account,
+
551 tokens,
+
552 asset1Out,
+
553 std::nullopt,
+
554 std::nullopt,
+
555 flags,
+
556 std::nullopt,
+
557 std::nullopt,
+
558 ter);
+
559}
+
560
+
561IOUAmount
+
562AMM::withdraw(
+
563 std::optional<Account> const& account,
+
564 STAmount const& asset1Out,
+
565 std::optional<STAmount> const& asset2Out,
+
566 std::optional<IOUAmount> const& maxEP,
+
567 std::optional<ter> const& ter)
+
568{
+
569 assert(!(asset2Out && maxEP));
+
570 return withdraw(
+
571 account,
572 std::nullopt,
-
573 std::nullopt,
-
574 ter);
-
575}
-
576
-
577IOUAmount
-
578AMM::withdraw(
-
579 std::optional<Account> const& account,
-
580 std::optional<LPToken> const& tokens,
-
581 std::optional<STAmount> const& asset1Out,
-
582 std::optional<STAmount> const& asset2Out,
-
583 std::optional<IOUAmount> const& maxEP,
-
584 std::optional<std::uint32_t> const& flags,
-
585 std::optional<std::pair<Issue, Issue>> const& assets,
-
586 std::optional<jtx::seq> const& seq,
-
587 std::optional<ter> const& ter)
-
588{
-
589 Json::Value jv;
-
590 if (tokens)
-
591 tokens->tokens(lptIssue_).setJson(jv[jss::LPTokenIn]);
-
592 if (asset1Out)
-
593 asset1Out->setJson(jv[jss::Amount]);
-
594 if (asset2Out)
-
595 asset2Out->setJson(jv[jss::Amount2]);
-
596 if (maxEP)
-
597 {
-
598 STAmount const saMaxEP{*maxEP, lptIssue_};
-
599 saMaxEP.setJson(jv[jss::EPrice]);
-
600 }
-
601 std::uint32_t jvflags = 0;
-
602 if (flags)
-
603 jvflags = *flags;
-
604 if (!(jvflags & tfWithdrawSubTx))
-
605 {
-
606 if (tokens && !asset1Out)
-
607 jvflags |= tfLPToken;
-
608 else if (asset1Out && asset2Out)
-
609 jvflags |= tfTwoAsset;
-
610 else if (tokens && asset1Out)
-
611 jvflags |= tfOneAssetLPToken;
-
612 else if (asset1Out && maxEP)
-
613 jvflags |= tfLimitLPToken;
-
614 else if (asset1Out)
-
615 jvflags |= tfSingleAsset;
-
616 }
-
617 jv[jss::Flags] = jvflags;
-
618 return withdraw(account, jv, seq, assets, ter);
-
619}
-
620
-
621IOUAmount
-
622AMM::withdraw(WithdrawArg const& arg)
-
623{
-
624 return withdraw(
-
625 arg.account,
-
626 arg.tokens,
-
627 arg.asset1Out,
-
628 arg.asset2Out,
-
629 arg.maxEP,
-
630 arg.flags,
-
631 arg.assets,
-
632 arg.seq,
-
633 arg.err);
-
634}
-
635
-
636void
-
637AMM::vote(
-
638 std::optional<Account> const& account,
-
639 std::uint32_t feeVal,
-
640 std::optional<std::uint32_t> const& flags,
-
641 std::optional<jtx::seq> const& seq,
-
642 std::optional<std::pair<Issue, Issue>> const& assets,
-
643 std::optional<ter> const& ter)
-
644{
-
645 Json::Value jv;
-
646 jv[jss::Account] = account ? account->human() : creatorAccount_.human();
-
647 setTokens(jv, assets);
-
648 jv[jss::TradingFee] = feeVal;
-
649 jv[jss::TransactionType] = jss::AMMVote;
-
650 if (flags)
-
651 jv[jss::Flags] = *flags;
-
652 if (fee_ != 0)
-
653 jv[jss::Fee] = std::to_string(fee_);
-
654 submit(jv, seq, ter);
-
655}
-
656
-
657void
-
658AMM::vote(VoteArg const& arg)
-
659{
-
660 return vote(arg.account, arg.tfee, arg.flags, arg.seq, arg.assets, arg.err);
-
661}
-
662
-
663Json::Value
-
664AMM::bid(BidArg const& arg)
-
665{
-
666 if (auto const amm =
-
667 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())))
-
668 {
-
669 assert(
-
670 !env_.current()->rules().enabled(fixInnerObjTemplate) ||
-
671 amm->isFieldPresent(sfAuctionSlot));
-
672 if (amm->isFieldPresent(sfAuctionSlot))
-
673 {
-
674 auto const& auctionSlot =
-
675 static_cast<STObject const&>(amm->peekAtField(sfAuctionSlot));
-
676 lastPurchasePrice_ = auctionSlot[sfPrice].iou();
-
677 }
-
678 }
-
679 bidMin_ = std::nullopt;
-
680 bidMax_ = std::nullopt;
-
681
-
682 Json::Value jv;
-
683 jv[jss::Account] =
-
684 arg.account ? arg.account->human() : creatorAccount_.human();
-
685 setTokens(jv, arg.assets);
-
686 auto getBid = [&](auto const& bid) {
-
687 if (std::holds_alternative<int>(bid))
-
688 return STAmount{lptIssue_, std::get<int>(bid)};
-
689 else if (std::holds_alternative<IOUAmount>(bid))
-
690 return toSTAmount(std::get<IOUAmount>(bid), lptIssue_);
-
691 else
-
692 return std::get<STAmount>(bid);
-
693 };
-
694 if (arg.bidMin)
-
695 {
-
696 STAmount saTokens = getBid(*arg.bidMin);
-
697 saTokens.setJson(jv[jss::BidMin]);
-
698 bidMin_ = saTokens.iou();
-
699 }
-
700 if (arg.bidMax)
-
701 {
-
702 STAmount saTokens = getBid(*arg.bidMax);
-
703 saTokens.setJson(jv[jss::BidMax]);
-
704 bidMax_ = saTokens.iou();
-
705 }
-
706 if (arg.authAccounts.size() > 0)
-
707 {
-
708 Json::Value accounts(Json::arrayValue);
-
709 for (auto const& account : arg.authAccounts)
-
710 {
-
711 Json::Value acct;
-
712 Json::Value authAcct;
-
713 acct[jss::Account] = account.human();
-
714 authAcct[jss::AuthAccount] = acct;
-
715 accounts.append(authAcct);
-
716 }
-
717 jv[jss::AuthAccounts] = accounts;
-
718 }
-
719 if (arg.flags)
-
720 jv[jss::Flags] = *arg.flags;
-
721 jv[jss::TransactionType] = jss::AMMBid;
-
722 if (fee_ != 0)
-
723 jv[jss::Fee] = std::to_string(fee_);
-
724 return jv;
-
725}
-
726
-
727void
-
728AMM::submit(
-
729 Json::Value const& jv,
-
730 std::optional<jtx::seq> const& seq,
-
731 std::optional<ter> const& ter)
-
732{
-
733 if (log_)
-
734 std::cout << jv.toStyledString();
-
735 if (msig_)
-
736 {
-
737 if (seq && ter)
-
738 env_(jv, *msig_, *seq, *ter);
-
739 else if (seq)
-
740 env_(jv, *msig_, *seq);
-
741 else if (ter)
-
742 env_(jv, *msig_, *ter);
-
743 else
-
744 env_(jv, *msig_);
-
745 }
-
746 else if (seq && ter)
-
747 env_(jv, *seq, *ter);
-
748 else if (seq)
-
749 env_(jv, *seq);
-
750 else if (ter)
-
751 env_(jv, *ter);
-
752 else
-
753 env_(jv);
-
754 if (doClose_)
-
755 env_.close();
-
756}
-
757
-
758bool
-
759AMM::expectAuctionSlot(auto&& cb) const
-
760{
-
761 if (auto const amm =
-
762 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())))
-
763 {
-
764 assert(
-
765 !env_.current()->rules().enabled(fixInnerObjTemplate) ||
-
766 amm->isFieldPresent(sfAuctionSlot));
-
767 if (amm->isFieldPresent(sfAuctionSlot))
-
768 {
-
769 auto const& auctionSlot =
-
770 static_cast<STObject const&>(amm->peekAtField(sfAuctionSlot));
-
771 if (auctionSlot.isFieldPresent(sfAccount))
-
772 {
-
773 // This could fail in pre-fixInnerObjTemplate tests
-
774 // if the submitted transactions recreate one of
-
775 // the failure scenarios. Access as optional
-
776 // to avoid the failure.
-
777 auto const slotFee = auctionSlot[~sfDiscountedFee].value_or(0);
-
778 auto const slotInterval = ammAuctionTimeSlot(
-
779 env_.app().timeKeeper().now().time_since_epoch().count(),
-
780 auctionSlot);
-
781 auto const slotPrice = auctionSlot[sfPrice].iou();
-
782 auto const authAccounts =
-
783 auctionSlot.getFieldArray(sfAuthAccounts);
-
784 return cb(slotFee, slotInterval, slotPrice, authAccounts);
-
785 }
-
786 }
-
787 }
-
788 return false;
-
789}
-
790
-
791void
-
792AMM::ammDelete(AccountID const& deleter, std::optional<ter> const& ter)
-
793{
-
794 Json::Value jv;
-
795 jv[jss::Account] = to_string(deleter);
-
796 setTokens(jv);
-
797 jv[jss::TransactionType] = jss::AMMDelete;
-
798 if (fee_ != 0)
-
799 jv[jss::Fee] = std::to_string(fee_);
-
800 submit(jv, std::nullopt, ter);
-
801}
-
802
-
803namespace amm {
-
804Json::Value
-
805trust(AccountID const& account, STAmount const& amount, std::uint32_t flags)
-
806{
-
807 if (isXRP(amount))
-
808 Throw<std::runtime_error>("trust() requires IOU");
-
809 Json::Value jv;
-
810 jv[jss::Account] = to_string(account);
-
811 jv[jss::LimitAmount] = amount.getJson(JsonOptions::none);
-
812 jv[jss::TransactionType] = jss::TrustSet;
-
813 jv[jss::Flags] = flags;
-
814 return jv;
-
815}
-
816Json::Value
-
817pay(Account const& account, AccountID const& to, STAmount const& amount)
-
818{
-
819 Json::Value jv;
-
820 jv[jss::Account] = account.human();
-
821 jv[jss::Amount] = amount.getJson(JsonOptions::none);
-
822 jv[jss::Destination] = to_string(to);
-
823 jv[jss::TransactionType] = jss::Payment;
-
824 return jv;
-
825}
-
826
-
827Json::Value
-
828ammClawback(
-
829 Account const& issuer,
-
830 Account const& holder,
-
831 Issue const& asset,
-
832 Issue const& asset2,
-
833 std::optional<STAmount> const& amount)
-
834{
-
835 Json::Value jv;
-
836 jv[jss::TransactionType] = jss::AMMClawback;
-
837 jv[jss::Account] = issuer.human();
-
838 jv[jss::Holder] = holder.human();
-
839 jv[jss::Asset] = to_json(asset);
-
840 jv[jss::Asset2] = to_json(asset2);
-
841 if (amount)
-
842 jv[jss::Amount] = amount->getJson(JsonOptions::none);
-
843
-
844 return jv;
-
845}
-
846} // namespace amm
-
847} // namespace jtx
-
848} // namespace test
-
849} // namespace ripple
+
573 asset1Out,
+
574 asset2Out,
+
575 maxEP,
+
576 std::nullopt,
+
577 std::nullopt,
+
578 std::nullopt,
+
579 ter);
+
580}
+
581
+
582IOUAmount
+
583AMM::withdraw(
+
584 std::optional<Account> const& account,
+
585 std::optional<LPToken> const& tokens,
+
586 std::optional<STAmount> const& asset1Out,
+
587 std::optional<STAmount> const& asset2Out,
+
588 std::optional<IOUAmount> const& maxEP,
+
589 std::optional<std::uint32_t> const& flags,
+
590 std::optional<std::pair<Issue, Issue>> const& assets,
+
591 std::optional<jtx::seq> const& seq,
+
592 std::optional<ter> const& ter)
+
593{
+
594 Json::Value jv;
+
595 if (tokens)
+
596 tokens->tokens(lptIssue_).setJson(jv[jss::LPTokenIn]);
+
597 if (asset1Out)
+
598 asset1Out->setJson(jv[jss::Amount]);
+
599 if (asset2Out)
+
600 asset2Out->setJson(jv[jss::Amount2]);
+
601 if (maxEP)
+
602 {
+
603 STAmount const saMaxEP{*maxEP, lptIssue_};
+
604 saMaxEP.setJson(jv[jss::EPrice]);
+
605 }
+
606 std::uint32_t jvflags = 0;
+
607 if (flags)
+
608 jvflags = *flags;
+
609 if (!(jvflags & tfWithdrawSubTx))
+
610 {
+
611 if (tokens && !asset1Out)
+
612 jvflags |= tfLPToken;
+
613 else if (asset1Out && asset2Out)
+
614 jvflags |= tfTwoAsset;
+
615 else if (tokens && asset1Out)
+
616 jvflags |= tfOneAssetLPToken;
+
617 else if (asset1Out && maxEP)
+
618 jvflags |= tfLimitLPToken;
+
619 else if (asset1Out)
+
620 jvflags |= tfSingleAsset;
+
621 }
+
622 jv[jss::Flags] = jvflags;
+
623 return withdraw(account, jv, seq, assets, ter);
+
624}
+
625
+
626IOUAmount
+
627AMM::withdraw(WithdrawArg const& arg)
+
628{
+
629 return withdraw(
+
630 arg.account,
+
631 arg.tokens,
+
632 arg.asset1Out,
+
633 arg.asset2Out,
+
634 arg.maxEP,
+
635 arg.flags,
+
636 arg.assets,
+
637 arg.seq,
+
638 arg.err);
+
639}
+
640
+
641void
+
642AMM::vote(
+
643 std::optional<Account> const& account,
+
644 std::uint32_t feeVal,
+
645 std::optional<std::uint32_t> const& flags,
+
646 std::optional<jtx::seq> const& seq,
+
647 std::optional<std::pair<Issue, Issue>> const& assets,
+
648 std::optional<ter> const& ter)
+
649{
+
650 Json::Value jv;
+
651 jv[jss::Account] = account ? account->human() : creatorAccount_.human();
+
652 setTokens(jv, assets);
+
653 jv[jss::TradingFee] = feeVal;
+
654 jv[jss::TransactionType] = jss::AMMVote;
+
655 if (flags)
+
656 jv[jss::Flags] = *flags;
+
657 if (fee_ != 0)
+
658 jv[jss::Fee] = std::to_string(fee_);
+
659 submit(jv, seq, ter);
+
660}
+
661
+
662void
+
663AMM::vote(VoteArg const& arg)
+
664{
+
665 return vote(arg.account, arg.tfee, arg.flags, arg.seq, arg.assets, arg.err);
+
666}
+
667
+
668Json::Value
+
669AMM::bid(BidArg const& arg)
+
670{
+
671 if (auto const amm =
+
672 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())))
+
673 {
+
674 assert(
+
675 !env_.current()->rules().enabled(fixInnerObjTemplate) ||
+
676 amm->isFieldPresent(sfAuctionSlot));
+
677 if (amm->isFieldPresent(sfAuctionSlot))
+
678 {
+
679 auto const& auctionSlot =
+
680 static_cast<STObject const&>(amm->peekAtField(sfAuctionSlot));
+
681 lastPurchasePrice_ = auctionSlot[sfPrice].iou();
+
682 }
+
683 }
+
684 bidMin_ = std::nullopt;
+
685 bidMax_ = std::nullopt;
+
686
+
687 Json::Value jv;
+
688 jv[jss::Account] =
+
689 arg.account ? arg.account->human() : creatorAccount_.human();
+
690 setTokens(jv, arg.assets);
+
691 auto getBid = [&](auto const& bid) {
+
692 if (std::holds_alternative<int>(bid))
+
693 return STAmount{lptIssue_, std::get<int>(bid)};
+
694 else if (std::holds_alternative<IOUAmount>(bid))
+
695 return toSTAmount(std::get<IOUAmount>(bid), lptIssue_);
+
696 else
+
697 return std::get<STAmount>(bid);
+
698 };
+
699 if (arg.bidMin)
+
700 {
+
701 STAmount saTokens = getBid(*arg.bidMin);
+
702 saTokens.setJson(jv[jss::BidMin]);
+
703 bidMin_ = saTokens.iou();
+
704 }
+
705 if (arg.bidMax)
+
706 {
+
707 STAmount saTokens = getBid(*arg.bidMax);
+
708 saTokens.setJson(jv[jss::BidMax]);
+
709 bidMax_ = saTokens.iou();
+
710 }
+
711 if (arg.authAccounts.size() > 0)
+
712 {
+
713 Json::Value accounts(Json::arrayValue);
+
714 for (auto const& account : arg.authAccounts)
+
715 {
+
716 Json::Value acct;
+
717 Json::Value authAcct;
+
718 acct[jss::Account] = account.human();
+
719 authAcct[jss::AuthAccount] = acct;
+
720 accounts.append(authAcct);
+
721 }
+
722 jv[jss::AuthAccounts] = accounts;
+
723 }
+
724 if (arg.flags)
+
725 jv[jss::Flags] = *arg.flags;
+
726 jv[jss::TransactionType] = jss::AMMBid;
+
727 if (fee_ != 0)
+
728 jv[jss::Fee] = std::to_string(fee_);
+
729 return jv;
+
730}
+
731
+
732void
+
733AMM::submit(
+
734 Json::Value const& jv,
+
735 std::optional<jtx::seq> const& seq,
+
736 std::optional<ter> const& ter)
+
737{
+
738 if (log_)
+
739 std::cout << jv.toStyledString();
+
740 if (msig_)
+
741 {
+
742 if (seq && ter)
+
743 env_(jv, *msig_, *seq, *ter);
+
744 else if (seq)
+
745 env_(jv, *msig_, *seq);
+
746 else if (ter)
+
747 env_(jv, *msig_, *ter);
+
748 else
+
749 env_(jv, *msig_);
+
750 }
+
751 else if (seq && ter)
+
752 env_(jv, *seq, *ter);
+
753 else if (seq)
+
754 env_(jv, *seq);
+
755 else if (ter)
+
756 env_(jv, *ter);
+
757 else
+
758 env_(jv);
+
759 if (doClose_)
+
760 env_.close();
+
761}
+
762
+
763bool
+
764AMM::expectAuctionSlot(auto&& cb) const
+
765{
+
766 if (auto const amm =
+
767 env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())))
+
768 {
+
769 assert(
+
770 !env_.current()->rules().enabled(fixInnerObjTemplate) ||
+
771 amm->isFieldPresent(sfAuctionSlot));
+
772 if (amm->isFieldPresent(sfAuctionSlot))
+
773 {
+
774 auto const& auctionSlot =
+
775 static_cast<STObject const&>(amm->peekAtField(sfAuctionSlot));
+
776 if (auctionSlot.isFieldPresent(sfAccount))
+
777 {
+
778 // This could fail in pre-fixInnerObjTemplate tests
+
779 // if the submitted transactions recreate one of
+
780 // the failure scenarios. Access as optional
+
781 // to avoid the failure.
+
782 auto const slotFee = auctionSlot[~sfDiscountedFee].value_or(0);
+
783 auto const slotInterval = ammAuctionTimeSlot(
+
784 env_.app().timeKeeper().now().time_since_epoch().count(),
+
785 auctionSlot);
+
786 auto const slotPrice = auctionSlot[sfPrice].iou();
+
787 auto const authAccounts =
+
788 auctionSlot.getFieldArray(sfAuthAccounts);
+
789 return cb(slotFee, slotInterval, slotPrice, authAccounts);
+
790 }
+
791 }
+
792 }
+
793 return false;
+
794}
+
795
+
796void
+
797AMM::ammDelete(AccountID const& deleter, std::optional<ter> const& ter)
+
798{
+
799 Json::Value jv;
+
800 jv[jss::Account] = to_string(deleter);
+
801 setTokens(jv);
+
802 jv[jss::TransactionType] = jss::AMMDelete;
+
803 if (fee_ != 0)
+
804 jv[jss::Fee] = std::to_string(fee_);
+
805 submit(jv, std::nullopt, ter);
+
806}
+
807
+
808namespace amm {
+
809Json::Value
+
810trust(AccountID const& account, STAmount const& amount, std::uint32_t flags)
+
811{
+
812 if (isXRP(amount))
+
813 Throw<std::runtime_error>("trust() requires IOU");
+
814 Json::Value jv;
+
815 jv[jss::Account] = to_string(account);
+
816 jv[jss::LimitAmount] = amount.getJson(JsonOptions::none);
+
817 jv[jss::TransactionType] = jss::TrustSet;
+
818 jv[jss::Flags] = flags;
+
819 return jv;
+
820}
+
821Json::Value
+
822pay(Account const& account, AccountID const& to, STAmount const& amount)
+
823{
+
824 Json::Value jv;
+
825 jv[jss::Account] = account.human();
+
826 jv[jss::Amount] = amount.getJson(JsonOptions::none);
+
827 jv[jss::Destination] = to_string(to);
+
828 jv[jss::TransactionType] = jss::Payment;
+
829 return jv;
+
830}
+
831
+
832Json::Value
+
833ammClawback(
+
834 Account const& issuer,
+
835 Account const& holder,
+
836 Issue const& asset,
+
837 Issue const& asset2,
+
838 std::optional<STAmount> const& amount)
+
839{
+
840 Json::Value jv;
+
841 jv[jss::TransactionType] = jss::AMMClawback;
+
842 jv[jss::Account] = issuer.human();
+
843 jv[jss::Holder] = holder.human();
+
844 jv[jss::Asset] = to_json(asset);
+
845 jv[jss::Asset2] = to_json(asset2);
+
846 if (amount)
+
847 jv[jss::Amount] = amount->getJson(JsonOptions::none);
+
848
+
849 return jv;
+
850}
+
851} // namespace amm
+
852} // namespace jtx
+
853} // namespace test
+
854} // namespace ripple
T cbegin(T... args)
Represents a JSON value.
Definition: json_value.h:150
Value & append(Value const &value)
Append value to array at the end.
Definition: json_value.cpp:910
@@ -947,49 +952,51 @@ $(function() {
time_point now() const override
Returns the current time, using the server's clock.
Definition: TimeKeeper.h:64
Convenience class to test AMM functionality.
Definition: AMM.h:124
-
std::optional< IOUAmount > bidMin_
Definition: AMM.h:135
-
Issue const lptIssue_
Definition: AMM.h:142
-
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
Definition: AMM.cpp:161
-
bool doClose_
Definition: AMM.h:132
-
void submit(Json::Value const &jv, std::optional< jtx::seq > const &seq, std::optional< ter > const &ter)
Definition: AMM.cpp:728
-
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:637
-
std::uint32_t const fee_
Definition: AMM.h:140
-
AccountID create(std::uint32_t tfee=0, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:129
-
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
Definition: AMM.cpp:275
+
std::optional< IOUAmount > bidMin_
Definition: AMM.h:134
+
Issue const lptIssue_
Definition: AMM.h:141
+
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
Definition: AMM.cpp:166
+
bool doClose_
Definition: AMM.h:131
+
void submit(Json::Value const &jv, std::optional< jtx::seq > const &seq, std::optional< ter > const &ter)
Definition: AMM.cpp:733
+
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:642
+
std::uint32_t const fee_
Definition: AMM.h:139
+
AccountID create(std::uint32_t tfee=0, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:134
+
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
Definition: AMM.cpp:280
STAmount const asset2_
Definition: AMM.h:128
-
std::tuple< STAmount, STAmount, STAmount > balances(Issue const &issue1, Issue const &issue2, std::optional< AccountID > const &account=std::nullopt) const
Get AMM balances for the token pair.
Definition: AMM.cpp:207
+
std::tuple< STAmount, STAmount, STAmount > balances(Issue const &issue1, Issue const &issue2, std::optional< AccountID > const &account=std::nullopt) const
Get AMM balances for the token pair.
Definition: AMM.cpp:212
STAmount const asset1_
Definition: AMM.h:127
-
void ammDelete(AccountID const &deleter, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:792
-
IOUAmount tokens() const
Definition: AMM.h:337
-
bool expectAmmInfo(STAmount const &asset1, STAmount const &asset2, IOUAmount const &balance, Json::Value const &jv) const
Definition: AMM.cpp:342
-
IOUAmount lastPurchasePrice_
Definition: AMM.h:134
-
AccountID const & ammAccount() const
Definition: AMM.h:325
-
AccountID const ammAccount_
Definition: AMM.h:141
-
bool expectTradingFee(std::uint16_t fee) const
Definition: AMM.cpp:312
-
std::optional< msig > const msig_
Definition: AMM.h:138
-
IOUAmount withdraw(std::optional< Account > const &account, std::optional< LPToken > const &tokens, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:537
-
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition: AMM.cpp:232
-
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:411
+
void ammDelete(AccountID const &deleter, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:797
+
IOUAmount tokens() const
Definition: AMM.h:343
+
bool expectAmmInfo(STAmount const &asset1, STAmount const &asset2, IOUAmount const &balance, Json::Value const &jv) const
Definition: AMM.cpp:347
+
IOUAmount lastPurchasePrice_
Definition: AMM.h:133
+
AccountID const & ammAccount() const
Definition: AMM.h:331
+
IOUAmount initialTokens()
Definition: AMM.cpp:44
+
AccountID const ammAccount_
Definition: AMM.h:140
+
bool expectTradingFee(std::uint16_t fee) const
Definition: AMM.cpp:317
+
std::optional< msig > const msig_
Definition: AMM.h:137
+
IOUAmount withdraw(std::optional< Account > const &account, std::optional< LPToken > const &tokens, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:542
+
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition: AMM.cpp:237
+
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:416
Account const creatorAccount_
Definition: AMM.h:126
-
std::optional< IOUAmount > bidMax_
Definition: AMM.h:136
+
std::optional< IOUAmount > bidMax_
Definition: AMM.h:135
Env & env_
Definition: AMM.h:125
-
IOUAmount getLPTokensBalance(std::optional< AccountID > const &account=std::nullopt) const
Definition: AMM.cpp:245
-
Json::Value bid(BidArg const &arg)
Definition: AMM.cpp:664
-
bool log_
Definition: AMM.h:131
-
bool expectAmmRpcInfo(STAmount const &asset1, STAmount const &asset2, IOUAmount const &balance, std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledger_index=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt) const
Definition: AMM.cpp:328
-
void setTokens(Json::Value &jv, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt)
Definition: AMM.cpp:371
-
AMM(Env &env, Account const &account, STAmount const &asset1, STAmount const &asset2, bool log=false, std::uint16_t tfee=0, std::uint32_t fee=0, std::optional< std::uint32_t > flags=std::nullopt, std::optional< jtx::seq > seq=std::nullopt, std::optional< jtx::msig > ms=std::nullopt, std::optional< ter > const &ter=std::nullopt, bool close=true)
Definition: AMM.cpp:50
-
bool ammExists() const
Definition: AMM.cpp:320
-
bool expectLPTokens(AccountID const &account, IOUAmount const &tokens) const
Definition: AMM.cpp:262
+
IOUAmount getLPTokensBalance(std::optional< AccountID > const &account=std::nullopt) const
Definition: AMM.cpp:250
+
Json::Value bid(BidArg const &arg)
Definition: AMM.cpp:669
+
bool log_
Definition: AMM.h:130
+
bool expectAmmRpcInfo(STAmount const &asset1, STAmount const &asset2, IOUAmount const &balance, std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledger_index=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt) const
Definition: AMM.cpp:333
+
void setTokens(Json::Value &jv, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt)
Definition: AMM.cpp:376
+
AMM(Env &env, Account const &account, STAmount const &asset1, STAmount const &asset2, bool log=false, std::uint16_t tfee=0, std::uint32_t fee=0, std::optional< std::uint32_t > flags=std::nullopt, std::optional< jtx::seq > seq=std::nullopt, std::optional< jtx::msig > ms=std::nullopt, std::optional< ter > const &ter=std::nullopt, bool close=true)
Definition: AMM.cpp:55
+
bool ammExists() const
Definition: AMM.cpp:325
+
bool expectLPTokens(AccountID const &account, IOUAmount const &tokens) const
Definition: AMM.cpp:267
Immutable cryptographic account descriptor.
Definition: Account.h:39
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
A transaction testing environment.
Definition: Env.h:121
TER ter() const
Return the TER for the last JTx.
Definition: Env.h:586
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:331
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
+
bool enabled(uint256 feature) const
Definition: Env.h:626
Application & app()
Definition: Env.h:261
beast::Journal const journal
Definition: Env.h:162
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
Definition: AMM.h:40
A balance matches.
Definition: balance.h:39
Set the fee on a JTx.
Definition: fee.h:37
@@ -1004,11 +1011,10 @@ $(function() {
static constexpr auto apiInvalidVersion
Definition: ApiVersion.h:56
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:446
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:184
-
Json::Value trust(AccountID const &account, STAmount const &amount, std::uint32_t flags=0)
Definition: AMM.cpp:805
-
Json::Value ammClawback(Account const &issuer, Account const &holder, Issue const &asset, Issue const &asset2, std::optional< STAmount > const &amount)
Definition: AMM.cpp:828
-
Json::Value pay(Account const &account, AccountID const &to, STAmount const &amount)
Definition: AMM.cpp:817
-
static Number number(STAmount const &a)
Definition: AMM.cpp:35
-
static IOUAmount initialTokens(STAmount const &asset1, STAmount const &asset2)
Definition: AMM.cpp:43
+
Json::Value trust(AccountID const &account, STAmount const &amount, std::uint32_t flags=0)
Definition: AMM.cpp:810
+
Json::Value ammClawback(Account const &issuer, Account const &holder, Issue const &asset, Issue const &asset2, std::optional< STAmount > const &amount)
Definition: AMM.cpp:833
+
Json::Value pay(Account const &account, AccountID const &to, STAmount const &amount)
Definition: AMM.cpp:822
+
static Number number(STAmount const &a)
Definition: AMM.cpp:36
Json::Value create(AccountID const &account, AccountID const &to, STAmount const &amount, NetClock::duration const &settleDelay, PublicKey const &pk, std::optional< NetClock::time_point > const &cancelAfter, std::optional< std::uint32_t > const &dstTag)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
constexpr std::uint32_t tfSingleAsset
Definition: TxFlags.h:217
diff --git a/AMM_8h_source.html b/AMM_8h_source.html index 0f2b18f1a7..286c01ae3d 100644 --- a/AMM_8h_source.html +++ b/AMM_8h_source.html @@ -203,19 +203,19 @@ $(function() {
127 STAmount const asset1_;
128 STAmount const asset2_;
129 uint256 const ammID_;
-
130 IOUAmount const initialLPTokens_;
-
131 bool log_;
-
132 bool doClose_;
-
133 // Predict next purchase price
-
134 IOUAmount lastPurchasePrice_;
-
135 std::optional<IOUAmount> bidMin_;
-
136 std::optional<IOUAmount> bidMax_;
-
137 // Multi-signature
-
138 std::optional<msig> const msig_;
-
139 // Transaction fee
-
140 std::uint32_t const fee_;
-
141 AccountID const ammAccount_;
-
142 Issue const lptIssue_;
+
130 bool log_;
+
131 bool doClose_;
+
132 // Predict next purchase price
+
133 IOUAmount lastPurchasePrice_;
+
134 std::optional<IOUAmount> bidMin_;
+
135 std::optional<IOUAmount> bidMax_;
+
136 // Multi-signature
+
137 std::optional<msig> const msig_;
+
138 // Transaction fee
+
139 std::uint32_t const fee_;
+
140 AccountID const ammAccount_;
+
141 Issue const lptIssue_;
+
142 IOUAmount const initialLPTokens_;
143
144public:
145 AMM(Env& env,
@@ -266,260 +266,269 @@ $(function() {
196 Issue const& issue2,
197 std::optional<AccountID> const& account = std::nullopt) const;
198
-
199 [[nodiscard]] bool
-
200 expectLPTokens(AccountID const& account, IOUAmount const& tokens) const;
-
201
-
207 [[nodiscard]] bool
-
208 expectAuctionSlot(
-
209 std::uint32_t fee,
-
210 std::optional<std::uint8_t> timeSlot,
-
211 IOUAmount expectedPrice) const;
-
212
+
199 std::tuple<STAmount, STAmount, STAmount>
+
200 balances(std::optional<AccountID> const& account = std::nullopt) const
+
201 {
+
202 return balances(asset1_.get<Issue>(), asset2_.get<Issue>(), account);
+
203 }
+
204
+
205 [[nodiscard]] bool
+
206 expectLPTokens(AccountID const& account, IOUAmount const& tokens) const;
+
207
213 [[nodiscard]] bool
-
214 expectAuctionSlot(std::vector<AccountID> const& authAccount) const;
-
215
-
216 [[nodiscard]] bool
-
217 expectTradingFee(std::uint16_t fee) const;
+
214 expectAuctionSlot(
+
215 std::uint32_t fee,
+
216 std::optional<std::uint8_t> timeSlot,
+
217 IOUAmount expectedPrice) const;
218
219 [[nodiscard]] bool
-
220 expectAmmRpcInfo(
-
221 STAmount const& asset1,
-
222 STAmount const& asset2,
-
223 IOUAmount const& balance,
-
224 std::optional<AccountID> const& account = std::nullopt,
-
225 std::optional<std::string> const& ledger_index = std::nullopt,
-
226 std::optional<AccountID> const& ammAccount = std::nullopt) const;
-
227
-
228 [[nodiscard]] bool
-
229 ammExists() const;
-
230
-
231 IOUAmount
-
232 deposit(
-
233 std::optional<Account> const& account,
-
234 LPToken tokens,
-
235 std::optional<STAmount> const& asset1InDetails = std::nullopt,
-
236 std::optional<std::uint32_t> const& flags = std::nullopt,
-
237 std::optional<ter> const& ter = std::nullopt);
-
238
-
239 IOUAmount
-
240 deposit(
-
241 std::optional<Account> const& account,
-
242 STAmount const& asset1InDetails,
-
243 std::optional<STAmount> const& asset2InAmount = std::nullopt,
-
244 std::optional<STAmount> const& maxEP = std::nullopt,
-
245 std::optional<std::uint32_t> const& flags = std::nullopt,
-
246 std::optional<ter> const& ter = std::nullopt);
-
247
-
248 IOUAmount
-
249 deposit(
-
250 std::optional<Account> const& account,
-
251 std::optional<LPToken> tokens,
-
252 std::optional<STAmount> const& asset1In,
-
253 std::optional<STAmount> const& asset2In,
-
254 std::optional<STAmount> const& maxEP,
-
255 std::optional<std::uint32_t> const& flags,
-
256 std::optional<std::pair<Issue, Issue>> const& assets,
-
257 std::optional<jtx::seq> const& seq,
-
258 std::optional<std::uint16_t> const& tfee = std::nullopt,
-
259 std::optional<ter> const& ter = std::nullopt);
-
260
-
261 IOUAmount
-
262 deposit(DepositArg const& arg);
-
263
-
264 IOUAmount
-
265 withdraw(
-
266 std::optional<Account> const& account,
-
267 std::optional<LPToken> const& tokens,
-
268 std::optional<STAmount> const& asset1OutDetails = std::nullopt,
-
269 std::optional<std::uint32_t> const& flags = std::nullopt,
-
270 std::optional<ter> const& ter = std::nullopt);
-
271
-
272 IOUAmount
-
273 withdrawAll(
-
274 std::optional<Account> const& account,
-
275 std::optional<STAmount> const& asset1OutDetails = std::nullopt,
-
276 std::optional<ter> const& ter = std::nullopt)
-
277 {
-
278 return withdraw(
-
279 account,
-
280 std::nullopt,
-
281 asset1OutDetails,
-
282 asset1OutDetails ? tfOneAssetWithdrawAll : tfWithdrawAll,
-
283 ter);
-
284 }
-
285
-
286 IOUAmount
-
287 withdraw(
-
288 std::optional<Account> const& account,
-
289 STAmount const& asset1Out,
-
290 std::optional<STAmount> const& asset2Out = std::nullopt,
-
291 std::optional<IOUAmount> const& maxEP = std::nullopt,
-
292 std::optional<ter> const& ter = std::nullopt);
-
293
-
294 IOUAmount
-
295 withdraw(
-
296 std::optional<Account> const& account,
-
297 std::optional<LPToken> const& tokens,
-
298 std::optional<STAmount> const& asset1Out,
-
299 std::optional<STAmount> const& asset2Out,
-
300 std::optional<IOUAmount> const& maxEP,
-
301 std::optional<std::uint32_t> const& flags,
-
302 std::optional<std::pair<Issue, Issue>> const& assets,
-
303 std::optional<jtx::seq> const& seq,
-
304 std::optional<ter> const& ter = std::nullopt);
-
305
-
306 IOUAmount
-
307 withdraw(WithdrawArg const& arg);
-
308
-
309 void
-
310 vote(
-
311 std::optional<Account> const& account,
-
312 std::uint32_t feeVal,
-
313 std::optional<std::uint32_t> const& flags = std::nullopt,
-
314 std::optional<jtx::seq> const& seq = std::nullopt,
-
315 std::optional<std::pair<Issue, Issue>> const& assets = std::nullopt,
-
316 std::optional<ter> const& ter = std::nullopt);
-
317
-
318 void
-
319 vote(VoteArg const& arg);
-
320
-
321 Json::Value
-
322 bid(BidArg const& arg);
+
220 expectAuctionSlot(std::vector<AccountID> const& authAccount) const;
+
221
+
222 [[nodiscard]] bool
+
223 expectTradingFee(std::uint16_t fee) const;
+
224
+
225 [[nodiscard]] bool
+
226 expectAmmRpcInfo(
+
227 STAmount const& asset1,
+
228 STAmount const& asset2,
+
229 IOUAmount const& balance,
+
230 std::optional<AccountID> const& account = std::nullopt,
+
231 std::optional<std::string> const& ledger_index = std::nullopt,
+
232 std::optional<AccountID> const& ammAccount = std::nullopt) const;
+
233
+
234 [[nodiscard]] bool
+
235 ammExists() const;
+
236
+
237 IOUAmount
+
238 deposit(
+
239 std::optional<Account> const& account,
+
240 LPToken tokens,
+
241 std::optional<STAmount> const& asset1InDetails = std::nullopt,
+
242 std::optional<std::uint32_t> const& flags = std::nullopt,
+
243 std::optional<ter> const& ter = std::nullopt);
+
244
+
245 IOUAmount
+
246 deposit(
+
247 std::optional<Account> const& account,
+
248 STAmount const& asset1InDetails,
+
249 std::optional<STAmount> const& asset2InAmount = std::nullopt,
+
250 std::optional<STAmount> const& maxEP = std::nullopt,
+
251 std::optional<std::uint32_t> const& flags = std::nullopt,
+
252 std::optional<ter> const& ter = std::nullopt);
+
253
+
254 IOUAmount
+
255 deposit(
+
256 std::optional<Account> const& account,
+
257 std::optional<LPToken> tokens,
+
258 std::optional<STAmount> const& asset1In,
+
259 std::optional<STAmount> const& asset2In,
+
260 std::optional<STAmount> const& maxEP,
+
261 std::optional<std::uint32_t> const& flags,
+
262 std::optional<std::pair<Issue, Issue>> const& assets,
+
263 std::optional<jtx::seq> const& seq,
+
264 std::optional<std::uint16_t> const& tfee = std::nullopt,
+
265 std::optional<ter> const& ter = std::nullopt);
+
266
+
267 IOUAmount
+
268 deposit(DepositArg const& arg);
+
269
+
270 IOUAmount
+
271 withdraw(
+
272 std::optional<Account> const& account,
+
273 std::optional<LPToken> const& tokens,
+
274 std::optional<STAmount> const& asset1OutDetails = std::nullopt,
+
275 std::optional<std::uint32_t> const& flags = std::nullopt,
+
276 std::optional<ter> const& ter = std::nullopt);
+
277
+
278 IOUAmount
+
279 withdrawAll(
+
280 std::optional<Account> const& account,
+
281 std::optional<STAmount> const& asset1OutDetails = std::nullopt,
+
282 std::optional<ter> const& ter = std::nullopt)
+
283 {
+
284 return withdraw(
+
285 account,
+
286 std::nullopt,
+
287 asset1OutDetails,
+
288 asset1OutDetails ? tfOneAssetWithdrawAll : tfWithdrawAll,
+
289 ter);
+
290 }
+
291
+
292 IOUAmount
+
293 withdraw(
+
294 std::optional<Account> const& account,
+
295 STAmount const& asset1Out,
+
296 std::optional<STAmount> const& asset2Out = std::nullopt,
+
297 std::optional<IOUAmount> const& maxEP = std::nullopt,
+
298 std::optional<ter> const& ter = std::nullopt);
+
299
+
300 IOUAmount
+
301 withdraw(
+
302 std::optional<Account> const& account,
+
303 std::optional<LPToken> const& tokens,
+
304 std::optional<STAmount> const& asset1Out,
+
305 std::optional<STAmount> const& asset2Out,
+
306 std::optional<IOUAmount> const& maxEP,
+
307 std::optional<std::uint32_t> const& flags,
+
308 std::optional<std::pair<Issue, Issue>> const& assets,
+
309 std::optional<jtx::seq> const& seq,
+
310 std::optional<ter> const& ter = std::nullopt);
+
311
+
312 IOUAmount
+
313 withdraw(WithdrawArg const& arg);
+
314
+
315 void
+
316 vote(
+
317 std::optional<Account> const& account,
+
318 std::uint32_t feeVal,
+
319 std::optional<std::uint32_t> const& flags = std::nullopt,
+
320 std::optional<jtx::seq> const& seq = std::nullopt,
+
321 std::optional<std::pair<Issue, Issue>> const& assets = std::nullopt,
+
322 std::optional<ter> const& ter = std::nullopt);
323
-
324 AccountID const&
-
325 ammAccount() const
-
326 {
-
327 return ammAccount_;
-
328 }
+
324 void
+
325 vote(VoteArg const& arg);
+
326
+
327 Json::Value
+
328 bid(BidArg const& arg);
329
-
330 Issue
-
331 lptIssue() const
+
330 AccountID const&
+
331 ammAccount() const
332 {
-
333 return lptIssue_;
+
333 return ammAccount_;
334 }
335
-
336 IOUAmount
-
337 tokens() const
+
336 Issue
+
337 lptIssue() const
338 {
-
339 return initialLPTokens_;
+
339 return lptIssue_;
340 }
341
342 IOUAmount
-
343 getLPTokensBalance(
-
344 std::optional<AccountID> const& account = std::nullopt) const;
-
345
-
346 friend std::ostream&
-
347 operator<<(std::ostream& s, AMM const& amm)
-
348 {
-
349 if (auto const res = amm.ammRpcInfo())
-
350 s << res.toStyledString();
-
351 return s;
-
352 }
-
353
-
354 std::string
-
355 operator[](AccountID const& lp)
-
356 {
-
357 return ammRpcInfo(lp).toStyledString();
+
343 tokens() const
+
344 {
+
345 return initialLPTokens_;
+
346 }
+
347
+
348 IOUAmount
+
349 getLPTokensBalance(
+
350 std::optional<AccountID> const& account = std::nullopt) const;
+
351
+
352 friend std::ostream&
+
353 operator<<(std::ostream& s, AMM const& amm)
+
354 {
+
355 if (auto const res = amm.ammRpcInfo())
+
356 s << res.toStyledString();
+
357 return s;
358 }
359
-
360 Json::Value
-
361 operator()(AccountID const& lp)
+
360 std::string
+
361 operator[](AccountID const& lp)
362 {
-
363 return ammRpcInfo(lp);
+
363 return ammRpcInfo(lp).toStyledString();
364 }
365
-
366 void
-
367 ammDelete(
-
368 AccountID const& deleter,
-
369 std::optional<ter> const& ter = std::nullopt);
-
370
-
371 void
-
372 setClose(bool close)
-
373 {
-
374 doClose_ = close;
-
375 }
+
366 Json::Value
+
367 operator()(AccountID const& lp)
+
368 {
+
369 return ammRpcInfo(lp);
+
370 }
+
371
+
372 void
+
373 ammDelete(
+
374 AccountID const& deleter,
+
375 std::optional<ter> const& ter = std::nullopt);
376
-
377 uint256
-
378 ammID() const
-
379 {
-
380 return ammID_;
+
377 void
+
378 setClose(bool close)
+
379 {
+
380 doClose_ = close;
381 }
382
-
383 void
-
384 setTokens(
-
385 Json::Value& jv,
-
386 std::optional<std::pair<Issue, Issue>> const& assets = std::nullopt);
-
387
-
388private:
-
389 AccountID
-
390 create(
-
391 std::uint32_t tfee = 0,
-
392 std::optional<std::uint32_t> const& flags = std::nullopt,
-
393 std::optional<jtx::seq> const& seq = std::nullopt,
-
394 std::optional<ter> const& ter = std::nullopt);
-
395
-
396 IOUAmount
-
397 deposit(
-
398 std::optional<Account> const& account,
-
399 Json::Value& jv,
-
400 std::optional<std::pair<Issue, Issue>> const& assets = std::nullopt,
-
401 std::optional<jtx::seq> const& seq = std::nullopt,
-
402 std::optional<ter> const& ter = std::nullopt);
-
403
-
404 IOUAmount
-
405 withdraw(
-
406 std::optional<Account> const& account,
-
407 Json::Value& jv,
-
408 std::optional<jtx::seq> const& seq,
-
409 std::optional<std::pair<Issue, Issue>> const& assets = std::nullopt,
-
410 std::optional<ter> const& ter = std::nullopt);
-
411
-
412 void
-
413 log(bool log)
-
414 {
-
415 log_ = log;
-
416 }
+
383 uint256
+
384 ammID() const
+
385 {
+
386 return ammID_;
+
387 }
+
388
+
389 void
+
390 setTokens(
+
391 Json::Value& jv,
+
392 std::optional<std::pair<Issue, Issue>> const& assets = std::nullopt);
+
393
+
394private:
+
395 AccountID
+
396 create(
+
397 std::uint32_t tfee = 0,
+
398 std::optional<std::uint32_t> const& flags = std::nullopt,
+
399 std::optional<jtx::seq> const& seq = std::nullopt,
+
400 std::optional<ter> const& ter = std::nullopt);
+
401
+
402 IOUAmount
+
403 deposit(
+
404 std::optional<Account> const& account,
+
405 Json::Value& jv,
+
406 std::optional<std::pair<Issue, Issue>> const& assets = std::nullopt,
+
407 std::optional<jtx::seq> const& seq = std::nullopt,
+
408 std::optional<ter> const& ter = std::nullopt);
+
409
+
410 IOUAmount
+
411 withdraw(
+
412 std::optional<Account> const& account,
+
413 Json::Value& jv,
+
414 std::optional<jtx::seq> const& seq,
+
415 std::optional<std::pair<Issue, Issue>> const& assets = std::nullopt,
+
416 std::optional<ter> const& ter = std::nullopt);
417
-
418 [[nodiscard]] bool
-
419 expectAmmInfo(
-
420 STAmount const& asset1,
-
421 STAmount const& asset2,
-
422 IOUAmount const& balance,
-
423 Json::Value const& jv) const;
-
424
-
425 void
-
426 submit(
-
427 Json::Value const& jv,
-
428 std::optional<jtx::seq> const& seq,
-
429 std::optional<ter> const& ter);
+
418 void
+
419 log(bool log)
+
420 {
+
421 log_ = log;
+
422 }
+
423
+
424 [[nodiscard]] bool
+
425 expectAmmInfo(
+
426 STAmount const& asset1,
+
427 STAmount const& asset2,
+
428 IOUAmount const& balance,
+
429 Json::Value const& jv) const;
430
-
431 [[nodiscard]] bool
-
432 expectAuctionSlot(auto&& cb) const;
-
433};
-
434
-
435namespace amm {
-
436Json::Value
-
437trust(
-
438 AccountID const& account,
-
439 STAmount const& amount,
-
440 std::uint32_t flags = 0);
-
441Json::Value
-
442pay(Account const& account, AccountID const& to, STAmount const& amount);
+
431 void
+
432 submit(
+
433 Json::Value const& jv,
+
434 std::optional<jtx::seq> const& seq,
+
435 std::optional<ter> const& ter);
+
436
+
437 [[nodiscard]] bool
+
438 expectAuctionSlot(auto&& cb) const;
+
439
+
440 IOUAmount
+
441 initialTokens();
+
442};
443
-
444Json::Value
-
445ammClawback(
-
446 Account const& issuer,
-
447 Account const& holder,
-
448 Issue const& asset,
-
449 Issue const& asset2,
-
450 std::optional<STAmount> const& amount);
-
451} // namespace amm
+
444namespace amm {
+
445Json::Value
+
446trust(
+
447 AccountID const& account,
+
448 STAmount const& amount,
+
449 std::uint32_t flags = 0);
+
450Json::Value
+
451pay(Account const& account, AccountID const& to, STAmount const& amount);
452
-
453} // namespace jtx
-
454} // namespace test
-
455} // namespace ripple
-
456
-
457#endif // RIPPLE_TEST_JTX_AMM_H_INCLUDED
+
453Json::Value
+
454ammClawback(
+
455 Account const& issuer,
+
456 Account const& holder,
+
457 Issue const& asset,
+
458 Issue const& asset2,
+
459 std::optional<STAmount> const& amount);
+
460} // namespace amm
+
461
+
462} // namespace jtx
+
463} // namespace test
+
464} // namespace ripple
+
465
+
466#endif // RIPPLE_TEST_JTX_AMM_H_INCLUDED
Represents a JSON value.
Definition: json_value.h:150
@@ -527,51 +536,53 @@ $(function() {
Floating point representation of amounts with high dynamic range.
Definition: IOUAmount.h:46
A currency issued by an account.
Definition: Issue.h:36
Definition: STAmount.h:50
+
constexpr TIss const & get() const
Convenience class to test AMM functionality.
Definition: AMM.h:124
-
std::optional< IOUAmount > bidMin_
Definition: AMM.h:135
-
Issue const lptIssue_
Definition: AMM.h:142
-
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
Definition: AMM.cpp:161
-
bool doClose_
Definition: AMM.h:132
-
void submit(Json::Value const &jv, std::optional< jtx::seq > const &seq, std::optional< ter > const &ter)
Definition: AMM.cpp:728
-
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:637
-
friend std::ostream & operator<<(std::ostream &s, AMM const &amm)
Definition: AMM.h:347
-
std::uint32_t const fee_
Definition: AMM.h:140
+
std::optional< IOUAmount > bidMin_
Definition: AMM.h:134
+
Issue const lptIssue_
Definition: AMM.h:141
+
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
Definition: AMM.cpp:166
+
bool doClose_
Definition: AMM.h:131
+
void submit(Json::Value const &jv, std::optional< jtx::seq > const &seq, std::optional< ter > const &ter)
Definition: AMM.cpp:733
+
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:642
+
friend std::ostream & operator<<(std::ostream &s, AMM const &amm)
Definition: AMM.h:353
+
std::uint32_t const fee_
Definition: AMM.h:139
uint256 const ammID_
Definition: AMM.h:129
-
AccountID create(std::uint32_t tfee=0, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:129
-
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
Definition: AMM.cpp:275
+
AccountID create(std::uint32_t tfee=0, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:134
+
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
Definition: AMM.cpp:280
STAmount const asset2_
Definition: AMM.h:128
-
std::string operator[](AccountID const &lp)
Definition: AMM.h:355
-
std::tuple< STAmount, STAmount, STAmount > balances(Issue const &issue1, Issue const &issue2, std::optional< AccountID > const &account=std::nullopt) const
Get AMM balances for the token pair.
Definition: AMM.cpp:207
+
std::string operator[](AccountID const &lp)
Definition: AMM.h:361
+
std::tuple< STAmount, STAmount, STAmount > balances(Issue const &issue1, Issue const &issue2, std::optional< AccountID > const &account=std::nullopt) const
Get AMM balances for the token pair.
Definition: AMM.cpp:212
STAmount const asset1_
Definition: AMM.h:127
-
void ammDelete(AccountID const &deleter, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:792
-
void log(bool log)
Definition: AMM.h:413
-
IOUAmount tokens() const
Definition: AMM.h:337
-
bool expectAmmInfo(STAmount const &asset1, STAmount const &asset2, IOUAmount const &balance, Json::Value const &jv) const
Definition: AMM.cpp:342
-
void setClose(bool close)
Definition: AMM.h:372
-
IOUAmount lastPurchasePrice_
Definition: AMM.h:134
-
AccountID const & ammAccount() const
Definition: AMM.h:325
-
AccountID const ammAccount_
Definition: AMM.h:141
-
bool expectTradingFee(std::uint16_t fee) const
Definition: AMM.cpp:312
-
Json::Value operator()(AccountID const &lp)
Definition: AMM.h:361
-
std::optional< msig > const msig_
Definition: AMM.h:138
-
IOUAmount withdraw(std::optional< Account > const &account, std::optional< LPToken > const &tokens, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:537
-
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition: AMM.cpp:232
-
Issue lptIssue() const
Definition: AMM.h:331
-
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:411
+
void ammDelete(AccountID const &deleter, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:797
+
void log(bool log)
Definition: AMM.h:419
+
IOUAmount tokens() const
Definition: AMM.h:343
+
bool expectAmmInfo(STAmount const &asset1, STAmount const &asset2, IOUAmount const &balance, Json::Value const &jv) const
Definition: AMM.cpp:347
+
void setClose(bool close)
Definition: AMM.h:378
+
IOUAmount lastPurchasePrice_
Definition: AMM.h:133
+
AccountID const & ammAccount() const
Definition: AMM.h:331
+
IOUAmount initialTokens()
Definition: AMM.cpp:44
+
AccountID const ammAccount_
Definition: AMM.h:140
+
bool expectTradingFee(std::uint16_t fee) const
Definition: AMM.cpp:317
+
Json::Value operator()(AccountID const &lp)
Definition: AMM.h:367
+
std::optional< msig > const msig_
Definition: AMM.h:137
+
IOUAmount withdraw(std::optional< Account > const &account, std::optional< LPToken > const &tokens, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:542
+
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition: AMM.cpp:237
+
Issue lptIssue() const
Definition: AMM.h:337
Account const creatorAccount_
Definition: AMM.h:126
-
std::optional< IOUAmount > bidMax_
Definition: AMM.h:136
-
IOUAmount const initialLPTokens_
Definition: AMM.h:130
+
std::tuple< STAmount, STAmount, STAmount > balances(std::optional< AccountID > const &account=std::nullopt) const
Definition: AMM.h:200
+
std::optional< IOUAmount > bidMax_
Definition: AMM.h:135
+
IOUAmount const initialLPTokens_
Definition: AMM.h:142
Env & env_
Definition: AMM.h:125
-
IOUAmount getLPTokensBalance(std::optional< AccountID > const &account=std::nullopt) const
Definition: AMM.cpp:245
-
Json::Value bid(BidArg const &arg)
Definition: AMM.cpp:664
-
bool log_
Definition: AMM.h:131
-
bool expectAmmRpcInfo(STAmount const &asset1, STAmount const &asset2, IOUAmount const &balance, std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledger_index=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt) const
Definition: AMM.cpp:328
-
void setTokens(Json::Value &jv, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt)
Definition: AMM.cpp:371
-
bool ammExists() const
Definition: AMM.cpp:320
-
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.h:273
-
uint256 ammID() const
Definition: AMM.h:378
-
bool expectLPTokens(AccountID const &account, IOUAmount const &tokens) const
Definition: AMM.cpp:262
+
IOUAmount getLPTokensBalance(std::optional< AccountID > const &account=std::nullopt) const
Definition: AMM.cpp:250
+
Json::Value bid(BidArg const &arg)
Definition: AMM.cpp:669
+
bool log_
Definition: AMM.h:130
+
bool expectAmmRpcInfo(STAmount const &asset1, STAmount const &asset2, IOUAmount const &balance, std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledger_index=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt) const
Definition: AMM.cpp:333
+
void setTokens(Json::Value &jv, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt)
Definition: AMM.cpp:376
+
bool ammExists() const
Definition: AMM.cpp:325
+
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.h:279
+
uint256 ammID() const
Definition: AMM.h:384
+
bool expectLPTokens(AccountID const &account, IOUAmount const &tokens) const
Definition: AMM.cpp:267
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:121
Definition: AMM.h:40
@@ -586,9 +597,9 @@ $(function() {
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:35
static constexpr auto apiInvalidVersion
Definition: ApiVersion.h:56
-
Json::Value trust(AccountID const &account, STAmount const &amount, std::uint32_t flags=0)
Definition: AMM.cpp:805
-
Json::Value ammClawback(Account const &issuer, Account const &holder, Issue const &asset, Issue const &asset2, std::optional< STAmount > const &amount)
Definition: AMM.cpp:828
-
Json::Value pay(Account const &account, AccountID const &to, STAmount const &amount)
Definition: AMM.cpp:817
+
Json::Value trust(AccountID const &account, STAmount const &amount, std::uint32_t flags=0)
Definition: AMM.cpp:810
+
Json::Value ammClawback(Account const &issuer, Account const &holder, Issue const &asset, Issue const &asset2, std::optional< STAmount > const &amount)
Definition: AMM.cpp:833
+
Json::Value pay(Account const &account, AccountID const &to, STAmount const &amount)
Definition: AMM.cpp:822
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
constexpr std::uint32_t tfOneAssetWithdrawAll
Definition: TxFlags.h:216
constexpr std::uint32_t tfWithdrawAll
Definition: TxFlags.h:215
diff --git a/AMM__test_8cpp_source.html b/AMM__test_8cpp_source.html index 930bda1915..1999f84c3e 100644 --- a/AMM__test_8cpp_source.html +++ b/AMM__test_8cpp_source.html @@ -105,7248 +105,7955 @@ $(function() {
27
28#include <xrpld/app/misc/AMMHelpers.h>
29#include <xrpld/app/misc/AMMUtils.h>
-
30#include <xrpld/app/tx/detail/AMMBid.h>
-
31
-
32#include <xrpl/basics/Number.h>
-
33#include <xrpl/protocol/AMMCore.h>
-
34#include <xrpl/protocol/Feature.h>
-
35#include <xrpl/protocol/TER.h>
-
36
-
37#include <boost/regex.hpp>
-
38
-
39#include <utility>
-
40#include <vector>
-
41
-
42namespace ripple {
-
43namespace test {
-
44
-
49struct AMM_test : public jtx::AMMTest
-
50{
-
51private:
-
52 void
-
53 testInstanceCreate()
-
54 {
-
55 testcase("Instance Create");
-
56
-
57 using namespace jtx;
-
58
-
59 // XRP to IOU, with featureSingleAssetVault
-
60 testAMM(
-
61 [&](AMM& ammAlice, Env&) {
-
62 BEAST_EXPECT(ammAlice.expectBalances(
-
63 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
-
64 },
-
65 {},
-
66 0,
-
67 {},
-
68 {supported_amendments() | featureSingleAssetVault});
-
69
-
70 // XRP to IOU, without featureSingleAssetVault
-
71 testAMM(
-
72 [&](AMM& ammAlice, Env&) {
-
73 BEAST_EXPECT(ammAlice.expectBalances(
-
74 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
-
75 },
-
76 {},
-
77 0,
-
78 {},
-
79 {supported_amendments() - featureSingleAssetVault});
-
80
-
81 // IOU to IOU
-
82 testAMM(
-
83 [&](AMM& ammAlice, Env&) {
-
84 BEAST_EXPECT(ammAlice.expectBalances(
-
85 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
-
86 },
-
87 {{USD(20'000), BTC(0.5)}});
-
88
-
89 // IOU to IOU + transfer fee
-
90 {
-
91 Env env{*this};
-
92 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
-
93 env(rate(gw, 1.25));
-
94 env.close();
-
95 // no transfer fee on create
-
96 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
-
97 BEAST_EXPECT(ammAlice.expectBalances(
-
98 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
-
99 BEAST_EXPECT(expectLine(env, alice, USD(0)));
-
100 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
-
101 }
-
102
-
103 // Require authorization is set, account is authorized
-
104 {
-
105 Env env{*this};
-
106 env.fund(XRP(30'000), gw, alice);
-
107 env.close();
-
108 env(fset(gw, asfRequireAuth));
-
109 env(trust(alice, gw["USD"](30'000), 0));
-
110 env(trust(gw, alice["USD"](0), tfSetfAuth));
-
111 env.close();
-
112 env(pay(gw, alice, USD(10'000)));
-
113 env.close();
-
114 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
-
115 }
-
116
-
117 // Cleared global freeze
-
118 {
-
119 Env env{*this};
-
120 env.fund(XRP(30'000), gw, alice);
-
121 env.close();
-
122 env.trust(USD(30'000), alice);
-
123 env.close();
-
124 env(pay(gw, alice, USD(10'000)));
-
125 env.close();
-
126 env(fset(gw, asfGlobalFreeze));
-
127 env.close();
-
128 AMM ammAliceFail(
-
129 env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
-
130 env(fclear(gw, asfGlobalFreeze));
-
131 env.close();
-
132 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
-
133 }
-
134
-
135 // Trading fee
-
136 testAMM(
-
137 [&](AMM& amm, Env&) {
-
138 BEAST_EXPECT(amm.expectTradingFee(1'000));
-
139 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
-
140 },
-
141 std::nullopt,
-
142 1'000);
-
143
-
144 // Make sure asset comparison works.
-
145 BEAST_EXPECT(
-
146 STIssue(sfAsset, STAmount(XRP(2'000)).issue()) ==
-
147 STIssue(sfAsset, STAmount(XRP(2'000)).issue()));
-
148 BEAST_EXPECT(
-
149 STIssue(sfAsset, STAmount(XRP(2'000)).issue()) !=
-
150 STIssue(sfAsset, STAmount(USD(2'000)).issue()));
-
151 }
-
152
-
153 void
-
154 testInvalidInstance()
-
155 {
-
156 testcase("Invalid Instance");
-
157
-
158 using namespace jtx;
-
159
-
160 // Can't have both XRP tokens
-
161 {
-
162 Env env{*this};
-
163 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
164 AMM ammAlice(
-
165 env, alice, XRP(10'000), XRP(10'000), ter(temBAD_AMM_TOKENS));
-
166 BEAST_EXPECT(!ammAlice.ammExists());
-
167 }
-
168
-
169 // Can't have both tokens the same IOU
-
170 {
-
171 Env env{*this};
-
172 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
173 AMM ammAlice(
-
174 env, alice, USD(10'000), USD(10'000), ter(temBAD_AMM_TOKENS));
-
175 BEAST_EXPECT(!ammAlice.ammExists());
-
176 }
-
177
-
178 // Can't have zero or negative amounts
-
179 {
-
180 Env env{*this};
-
181 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
182 AMM ammAlice(env, alice, XRP(0), USD(10'000), ter(temBAD_AMOUNT));
-
183 BEAST_EXPECT(!ammAlice.ammExists());
-
184 AMM ammAlice1(env, alice, XRP(10'000), USD(0), ter(temBAD_AMOUNT));
-
185 BEAST_EXPECT(!ammAlice1.ammExists());
-
186 AMM ammAlice2(
-
187 env, alice, XRP(10'000), USD(-10'000), ter(temBAD_AMOUNT));
-
188 BEAST_EXPECT(!ammAlice2.ammExists());
-
189 AMM ammAlice3(
-
190 env, alice, XRP(-10'000), USD(10'000), ter(temBAD_AMOUNT));
-
191 BEAST_EXPECT(!ammAlice3.ammExists());
-
192 }
-
193
-
194 // Bad currency
-
195 {
-
196 Env env{*this};
-
197 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
198 AMM ammAlice(
-
199 env, alice, XRP(10'000), BAD(10'000), ter(temBAD_CURRENCY));
-
200 BEAST_EXPECT(!ammAlice.ammExists());
-
201 }
-
202
-
203 // Insufficient IOU balance
-
204 {
-
205 Env env{*this};
-
206 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
207 AMM ammAlice(
-
208 env, alice, XRP(10'000), USD(40'000), ter(tecUNFUNDED_AMM));
-
209 BEAST_EXPECT(!ammAlice.ammExists());
-
210 }
-
211
-
212 // Insufficient XRP balance
-
213 {
-
214 Env env{*this};
-
215 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
216 AMM ammAlice(
-
217 env, alice, XRP(40'000), USD(10'000), ter(tecUNFUNDED_AMM));
-
218 BEAST_EXPECT(!ammAlice.ammExists());
-
219 }
-
220
-
221 // Invalid trading fee
-
222 {
-
223 Env env{*this};
-
224 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
225 AMM ammAlice(
-
226 env,
-
227 alice,
-
228 XRP(10'000),
-
229 USD(10'000),
-
230 false,
-
231 65'001,
-
232 10,
-
233 std::nullopt,
+
30#include <xrpld/app/paths/AMMContext.h>
+
31#include <xrpld/app/tx/detail/AMMBid.h>
+
32
+
33#include <xrpl/basics/Number.h>
+
34#include <xrpl/protocol/AMMCore.h>
+
35#include <xrpl/protocol/Feature.h>
+
36#include <xrpl/protocol/TER.h>
+
37
+
38#include <boost/regex.hpp>
+
39
+
40#include <utility>
+
41#include <vector>
+
42
+
43namespace ripple {
+
44namespace test {
+
45
+
50struct AMM_test : public jtx::AMMTest
+
51{
+
52private:
+
53 void
+
54 testInstanceCreate()
+
55 {
+
56 testcase("Instance Create");
+
57
+
58 using namespace jtx;
+
59
+
60 // XRP to IOU, with featureSingleAssetVault
+
61 testAMM(
+
62 [&](AMM& ammAlice, Env&) {
+
63 BEAST_EXPECT(ammAlice.expectBalances(
+
64 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
+
65 },
+
66 {},
+
67 0,
+
68 {},
+
69 {supported_amendments() | featureSingleAssetVault});
+
70
+
71 // XRP to IOU, without featureSingleAssetVault
+
72 testAMM(
+
73 [&](AMM& ammAlice, Env&) {
+
74 BEAST_EXPECT(ammAlice.expectBalances(
+
75 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
+
76 },
+
77 {},
+
78 0,
+
79 {},
+
80 {supported_amendments() - featureSingleAssetVault});
+
81
+
82 // IOU to IOU
+
83 testAMM(
+
84 [&](AMM& ammAlice, Env&) {
+
85 BEAST_EXPECT(ammAlice.expectBalances(
+
86 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
+
87 },
+
88 {{USD(20'000), BTC(0.5)}});
+
89
+
90 // IOU to IOU + transfer fee
+
91 {
+
92 Env env{*this};
+
93 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
+
94 env(rate(gw, 1.25));
+
95 env.close();
+
96 // no transfer fee on create
+
97 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
+
98 BEAST_EXPECT(ammAlice.expectBalances(
+
99 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
+
100 BEAST_EXPECT(expectLine(env, alice, USD(0)));
+
101 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
+
102 }
+
103
+
104 // Require authorization is set, account is authorized
+
105 {
+
106 Env env{*this};
+
107 env.fund(XRP(30'000), gw, alice);
+
108 env.close();
+
109 env(fset(gw, asfRequireAuth));
+
110 env(trust(alice, gw["USD"](30'000), 0));
+
111 env(trust(gw, alice["USD"](0), tfSetfAuth));
+
112 env.close();
+
113 env(pay(gw, alice, USD(10'000)));
+
114 env.close();
+
115 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
+
116 }
+
117
+
118 // Cleared global freeze
+
119 {
+
120 Env env{*this};
+
121 env.fund(XRP(30'000), gw, alice);
+
122 env.close();
+
123 env.trust(USD(30'000), alice);
+
124 env.close();
+
125 env(pay(gw, alice, USD(10'000)));
+
126 env.close();
+
127 env(fset(gw, asfGlobalFreeze));
+
128 env.close();
+
129 AMM ammAliceFail(
+
130 env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
+
131 env(fclear(gw, asfGlobalFreeze));
+
132 env.close();
+
133 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
+
134 }
+
135
+
136 // Trading fee
+
137 testAMM(
+
138 [&](AMM& amm, Env&) {
+
139 BEAST_EXPECT(amm.expectTradingFee(1'000));
+
140 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
+
141 },
+
142 std::nullopt,
+
143 1'000);
+
144
+
145 // Make sure asset comparison works.
+
146 BEAST_EXPECT(
+
147 STIssue(sfAsset, STAmount(XRP(2'000)).issue()) ==
+
148 STIssue(sfAsset, STAmount(XRP(2'000)).issue()));
+
149 BEAST_EXPECT(
+
150 STIssue(sfAsset, STAmount(XRP(2'000)).issue()) !=
+
151 STIssue(sfAsset, STAmount(USD(2'000)).issue()));
+
152 }
+
153
+
154 void
+
155 testInvalidInstance()
+
156 {
+
157 testcase("Invalid Instance");
+
158
+
159 using namespace jtx;
+
160
+
161 // Can't have both XRP tokens
+
162 {
+
163 Env env{*this};
+
164 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
165 AMM ammAlice(
+
166 env, alice, XRP(10'000), XRP(10'000), ter(temBAD_AMM_TOKENS));
+
167 BEAST_EXPECT(!ammAlice.ammExists());
+
168 }
+
169
+
170 // Can't have both tokens the same IOU
+
171 {
+
172 Env env{*this};
+
173 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
174 AMM ammAlice(
+
175 env, alice, USD(10'000), USD(10'000), ter(temBAD_AMM_TOKENS));
+
176 BEAST_EXPECT(!ammAlice.ammExists());
+
177 }
+
178
+
179 // Can't have zero or negative amounts
+
180 {
+
181 Env env{*this};
+
182 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
183 AMM ammAlice(env, alice, XRP(0), USD(10'000), ter(temBAD_AMOUNT));
+
184 BEAST_EXPECT(!ammAlice.ammExists());
+
185 AMM ammAlice1(env, alice, XRP(10'000), USD(0), ter(temBAD_AMOUNT));
+
186 BEAST_EXPECT(!ammAlice1.ammExists());
+
187 AMM ammAlice2(
+
188 env, alice, XRP(10'000), USD(-10'000), ter(temBAD_AMOUNT));
+
189 BEAST_EXPECT(!ammAlice2.ammExists());
+
190 AMM ammAlice3(
+
191 env, alice, XRP(-10'000), USD(10'000), ter(temBAD_AMOUNT));
+
192 BEAST_EXPECT(!ammAlice3.ammExists());
+
193 }
+
194
+
195 // Bad currency
+
196 {
+
197 Env env{*this};
+
198 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
199 AMM ammAlice(
+
200 env, alice, XRP(10'000), BAD(10'000), ter(temBAD_CURRENCY));
+
201 BEAST_EXPECT(!ammAlice.ammExists());
+
202 }
+
203
+
204 // Insufficient IOU balance
+
205 {
+
206 Env env{*this};
+
207 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
208 AMM ammAlice(
+
209 env, alice, XRP(10'000), USD(40'000), ter(tecUNFUNDED_AMM));
+
210 BEAST_EXPECT(!ammAlice.ammExists());
+
211 }
+
212
+
213 // Insufficient XRP balance
+
214 {
+
215 Env env{*this};
+
216 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
217 AMM ammAlice(
+
218 env, alice, XRP(40'000), USD(10'000), ter(tecUNFUNDED_AMM));
+
219 BEAST_EXPECT(!ammAlice.ammExists());
+
220 }
+
221
+
222 // Invalid trading fee
+
223 {
+
224 Env env{*this};
+
225 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
226 AMM ammAlice(
+
227 env,
+
228 alice,
+
229 XRP(10'000),
+
230 USD(10'000),
+
231 false,
+
232 65'001,
+
233 10,
234 std::nullopt,
235 std::nullopt,
-
236 ter(temBAD_FEE));
-
237 BEAST_EXPECT(!ammAlice.ammExists());
-
238 }
-
239
-
240 // AMM already exists
-
241 testAMM([&](AMM& ammAlice, Env& env) {
-
242 AMM ammCarol(
-
243 env, carol, XRP(10'000), USD(10'000), ter(tecDUPLICATE));
-
244 });
-
245
-
246 // Invalid flags
-
247 {
-
248 Env env{*this};
-
249 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
250 AMM ammAlice(
-
251 env,
-
252 alice,
-
253 XRP(10'000),
-
254 USD(10'000),
-
255 false,
-
256 0,
-
257 10,
-
258 tfWithdrawAll,
-
259 std::nullopt,
+
236 std::nullopt,
+
237 ter(temBAD_FEE));
+
238 BEAST_EXPECT(!ammAlice.ammExists());
+
239 }
+
240
+
241 // AMM already exists
+
242 testAMM([&](AMM& ammAlice, Env& env) {
+
243 AMM ammCarol(
+
244 env, carol, XRP(10'000), USD(10'000), ter(tecDUPLICATE));
+
245 });
+
246
+
247 // Invalid flags
+
248 {
+
249 Env env{*this};
+
250 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
251 AMM ammAlice(
+
252 env,
+
253 alice,
+
254 XRP(10'000),
+
255 USD(10'000),
+
256 false,
+
257 0,
+
258 10,
+
259 tfWithdrawAll,
260 std::nullopt,
-
261 ter(temINVALID_FLAG));
-
262 BEAST_EXPECT(!ammAlice.ammExists());
-
263 }
-
264
-
265 // Invalid Account
-
266 {
-
267 Env env{*this};
-
268 Account bad("bad");
-
269 env.memoize(bad);
-
270 AMM ammAlice(
-
271 env,
-
272 bad,
-
273 XRP(10'000),
-
274 USD(10'000),
-
275 false,
-
276 0,
-
277 10,
-
278 std::nullopt,
-
279 seq(1),
-
280 std::nullopt,
-
281 ter(terNO_ACCOUNT));
-
282 BEAST_EXPECT(!ammAlice.ammExists());
-
283 }
-
284
-
285 // Require authorization is set
-
286 {
-
287 Env env{*this};
-
288 env.fund(XRP(30'000), gw, alice);
-
289 env.close();
-
290 env(fset(gw, asfRequireAuth));
-
291 env.close();
-
292 env(trust(gw, alice["USD"](30'000)));
-
293 env.close();
-
294 AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecNO_AUTH));
-
295 BEAST_EXPECT(!ammAlice.ammExists());
-
296 }
-
297
-
298 // Globally frozen
-
299 {
-
300 Env env{*this};
-
301 env.fund(XRP(30'000), gw, alice);
-
302 env.close();
-
303 env(fset(gw, asfGlobalFreeze));
-
304 env.close();
-
305 env(trust(gw, alice["USD"](30'000)));
-
306 env.close();
-
307 AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
-
308 BEAST_EXPECT(!ammAlice.ammExists());
-
309 }
-
310
-
311 // Individually frozen
-
312 {
-
313 Env env{*this};
-
314 env.fund(XRP(30'000), gw, alice);
-
315 env.close();
-
316 env(trust(gw, alice["USD"](30'000)));
-
317 env.close();
-
318 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
319 env.close();
-
320 AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
-
321 BEAST_EXPECT(!ammAlice.ammExists());
-
322 }
-
323
-
324 // Insufficient reserve, XRP/IOU
-
325 {
-
326 Env env(*this);
-
327 auto const starting_xrp =
-
328 XRP(1'000) + reserve(env, 3) + env.current()->fees().base * 4;
-
329 env.fund(starting_xrp, gw);
-
330 env.fund(starting_xrp, alice);
-
331 env.trust(USD(2'000), alice);
-
332 env.close();
-
333 env(pay(gw, alice, USD(2'000)));
-
334 env.close();
-
335 env(offer(alice, XRP(101), USD(100)));
-
336 env(offer(alice, XRP(102), USD(100)));
-
337 AMM ammAlice(
-
338 env, alice, XRP(1'000), USD(1'000), ter(tecUNFUNDED_AMM));
-
339 }
-
340
-
341 // Insufficient reserve, IOU/IOU
-
342 {
-
343 Env env(*this);
-
344 auto const starting_xrp =
-
345 reserve(env, 4) + env.current()->fees().base * 5;
-
346 env.fund(starting_xrp, gw);
-
347 env.fund(starting_xrp, alice);
-
348 env.trust(USD(2'000), alice);
-
349 env.trust(EUR(2'000), alice);
-
350 env.close();
-
351 env(pay(gw, alice, USD(2'000)));
-
352 env(pay(gw, alice, EUR(2'000)));
-
353 env.close();
-
354 env(offer(alice, EUR(101), USD(100)));
-
355 env(offer(alice, EUR(102), USD(100)));
-
356 AMM ammAlice(
-
357 env, alice, EUR(1'000), USD(1'000), ter(tecINSUF_RESERVE_LINE));
-
358 }
-
359
-
360 // Insufficient fee
-
361 {
-
362 Env env(*this);
-
363 fund(env, gw, {alice}, XRP(2'000), {USD(2'000), EUR(2'000)});
-
364 AMM ammAlice(
-
365 env,
-
366 alice,
-
367 EUR(1'000),
-
368 USD(1'000),
-
369 false,
-
370 0,
-
371 ammCrtFee(env).drops() - 1,
-
372 std::nullopt,
+
261 std::nullopt,
+
262 ter(temINVALID_FLAG));
+
263 BEAST_EXPECT(!ammAlice.ammExists());
+
264 }
+
265
+
266 // Invalid Account
+
267 {
+
268 Env env{*this};
+
269 Account bad("bad");
+
270 env.memoize(bad);
+
271 AMM ammAlice(
+
272 env,
+
273 bad,
+
274 XRP(10'000),
+
275 USD(10'000),
+
276 false,
+
277 0,
+
278 10,
+
279 std::nullopt,
+
280 seq(1),
+
281 std::nullopt,
+
282 ter(terNO_ACCOUNT));
+
283 BEAST_EXPECT(!ammAlice.ammExists());
+
284 }
+
285
+
286 // Require authorization is set
+
287 {
+
288 Env env{*this};
+
289 env.fund(XRP(30'000), gw, alice);
+
290 env.close();
+
291 env(fset(gw, asfRequireAuth));
+
292 env.close();
+
293 env(trust(gw, alice["USD"](30'000)));
+
294 env.close();
+
295 AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecNO_AUTH));
+
296 BEAST_EXPECT(!ammAlice.ammExists());
+
297 }
+
298
+
299 // Globally frozen
+
300 {
+
301 Env env{*this};
+
302 env.fund(XRP(30'000), gw, alice);
+
303 env.close();
+
304 env(fset(gw, asfGlobalFreeze));
+
305 env.close();
+
306 env(trust(gw, alice["USD"](30'000)));
+
307 env.close();
+
308 AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
+
309 BEAST_EXPECT(!ammAlice.ammExists());
+
310 }
+
311
+
312 // Individually frozen
+
313 {
+
314 Env env{*this};
+
315 env.fund(XRP(30'000), gw, alice);
+
316 env.close();
+
317 env(trust(gw, alice["USD"](30'000)));
+
318 env.close();
+
319 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
320 env.close();
+
321 AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
+
322 BEAST_EXPECT(!ammAlice.ammExists());
+
323 }
+
324
+
325 // Insufficient reserve, XRP/IOU
+
326 {
+
327 Env env(*this);
+
328 auto const starting_xrp =
+
329 XRP(1'000) + reserve(env, 3) + env.current()->fees().base * 4;
+
330 env.fund(starting_xrp, gw);
+
331 env.fund(starting_xrp, alice);
+
332 env.trust(USD(2'000), alice);
+
333 env.close();
+
334 env(pay(gw, alice, USD(2'000)));
+
335 env.close();
+
336 env(offer(alice, XRP(101), USD(100)));
+
337 env(offer(alice, XRP(102), USD(100)));
+
338 AMM ammAlice(
+
339 env, alice, XRP(1'000), USD(1'000), ter(tecUNFUNDED_AMM));
+
340 }
+
341
+
342 // Insufficient reserve, IOU/IOU
+
343 {
+
344 Env env(*this);
+
345 auto const starting_xrp =
+
346 reserve(env, 4) + env.current()->fees().base * 5;
+
347 env.fund(starting_xrp, gw);
+
348 env.fund(starting_xrp, alice);
+
349 env.trust(USD(2'000), alice);
+
350 env.trust(EUR(2'000), alice);
+
351 env.close();
+
352 env(pay(gw, alice, USD(2'000)));
+
353 env(pay(gw, alice, EUR(2'000)));
+
354 env.close();
+
355 env(offer(alice, EUR(101), USD(100)));
+
356 env(offer(alice, EUR(102), USD(100)));
+
357 AMM ammAlice(
+
358 env, alice, EUR(1'000), USD(1'000), ter(tecINSUF_RESERVE_LINE));
+
359 }
+
360
+
361 // Insufficient fee
+
362 {
+
363 Env env(*this);
+
364 fund(env, gw, {alice}, XRP(2'000), {USD(2'000), EUR(2'000)});
+
365 AMM ammAlice(
+
366 env,
+
367 alice,
+
368 EUR(1'000),
+
369 USD(1'000),
+
370 false,
+
371 0,
+
372 ammCrtFee(env).drops() - 1,
373 std::nullopt,
374 std::nullopt,
-
375 ter(telINSUF_FEE_P));
-
376 }
-
377
-
378 // AMM with LPTokens
-
379
-
380 // AMM with one LPToken from another AMM.
-
381 testAMM([&](AMM& ammAlice, Env& env) {
-
382 fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly);
-
383 AMM ammAMMToken(
-
384 env,
-
385 alice,
-
386 EUR(10'000),
-
387 STAmount{ammAlice.lptIssue(), 1'000'000},
-
388 ter(tecAMM_INVALID_TOKENS));
-
389 AMM ammAMMToken1(
-
390 env,
-
391 alice,
-
392 STAmount{ammAlice.lptIssue(), 1'000'000},
-
393 EUR(10'000),
-
394 ter(tecAMM_INVALID_TOKENS));
-
395 });
-
396
-
397 // AMM with two LPTokens from other AMMs.
-
398 testAMM([&](AMM& ammAlice, Env& env) {
-
399 fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly);
-
400 AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000));
-
401 auto const token1 = ammAlice.lptIssue();
-
402 auto const token2 = ammAlice1.lptIssue();
-
403 AMM ammAMMTokens(
-
404 env,
-
405 alice,
-
406 STAmount{token1, 1'000'000},
-
407 STAmount{token2, 1'000'000},
-
408 ter(tecAMM_INVALID_TOKENS));
-
409 });
-
410
-
411 // Issuer has DefaultRipple disabled
-
412 {
-
413 Env env(*this);
-
414 env.fund(XRP(30'000), gw);
-
415 env(fclear(gw, asfDefaultRipple));
-
416 AMM ammGw(env, gw, XRP(10'000), USD(10'000), ter(terNO_RIPPLE));
-
417 env.fund(XRP(30'000), alice);
-
418 env.trust(USD(30'000), alice);
-
419 env(pay(gw, alice, USD(30'000)));
-
420 AMM ammAlice(
-
421 env, alice, XRP(10'000), USD(10'000), ter(terNO_RIPPLE));
-
422 Account const gw1("gw1");
-
423 env.fund(XRP(30'000), gw1);
-
424 env(fclear(gw1, asfDefaultRipple));
-
425 env.trust(USD(30'000), gw1);
-
426 env(pay(gw, gw1, USD(30'000)));
-
427 auto const USD1 = gw1["USD"];
-
428 AMM ammGwGw1(env, gw, USD(10'000), USD1(10'000), ter(terNO_RIPPLE));
-
429 env.trust(USD1(30'000), alice);
-
430 env(pay(gw1, alice, USD1(30'000)));
-
431 AMM ammAlice1(
-
432 env, alice, USD(10'000), USD1(10'000), ter(terNO_RIPPLE));
-
433 }
-
434 }
-
435
-
436 void
-
437 testInvalidDeposit(FeatureBitset features)
-
438 {
-
439 testcase("Invalid Deposit");
-
440
-
441 using namespace jtx;
-
442
-
443 testAMM([&](AMM& ammAlice, Env& env) {
-
444 // Invalid flags
-
445 ammAlice.deposit(
-
446 alice,
-
447 1'000'000,
-
448 std::nullopt,
-
449 tfWithdrawAll,
-
450 ter(temINVALID_FLAG));
-
451
-
452 // Invalid options
-
453 std::vector<std::tuple<
-
454 std::optional<std::uint32_t>,
+
375 std::nullopt,
+
376 ter(telINSUF_FEE_P));
+
377 }
+
378
+
379 // AMM with LPTokens
+
380
+
381 // AMM with one LPToken from another AMM.
+
382 testAMM([&](AMM& ammAlice, Env& env) {
+
383 fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly);
+
384 AMM ammAMMToken(
+
385 env,
+
386 alice,
+
387 EUR(10'000),
+
388 STAmount{ammAlice.lptIssue(), 1'000'000},
+
389 ter(tecAMM_INVALID_TOKENS));
+
390 AMM ammAMMToken1(
+
391 env,
+
392 alice,
+
393 STAmount{ammAlice.lptIssue(), 1'000'000},
+
394 EUR(10'000),
+
395 ter(tecAMM_INVALID_TOKENS));
+
396 });
+
397
+
398 // AMM with two LPTokens from other AMMs.
+
399 testAMM([&](AMM& ammAlice, Env& env) {
+
400 fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly);
+
401 AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000));
+
402 auto const token1 = ammAlice.lptIssue();
+
403 auto const token2 = ammAlice1.lptIssue();
+
404 AMM ammAMMTokens(
+
405 env,
+
406 alice,
+
407 STAmount{token1, 1'000'000},
+
408 STAmount{token2, 1'000'000},
+
409 ter(tecAMM_INVALID_TOKENS));
+
410 });
+
411
+
412 // Issuer has DefaultRipple disabled
+
413 {
+
414 Env env(*this);
+
415 env.fund(XRP(30'000), gw);
+
416 env(fclear(gw, asfDefaultRipple));
+
417 AMM ammGw(env, gw, XRP(10'000), USD(10'000), ter(terNO_RIPPLE));
+
418 env.fund(XRP(30'000), alice);
+
419 env.trust(USD(30'000), alice);
+
420 env(pay(gw, alice, USD(30'000)));
+
421 AMM ammAlice(
+
422 env, alice, XRP(10'000), USD(10'000), ter(terNO_RIPPLE));
+
423 Account const gw1("gw1");
+
424 env.fund(XRP(30'000), gw1);
+
425 env(fclear(gw1, asfDefaultRipple));
+
426 env.trust(USD(30'000), gw1);
+
427 env(pay(gw, gw1, USD(30'000)));
+
428 auto const USD1 = gw1["USD"];
+
429 AMM ammGwGw1(env, gw, USD(10'000), USD1(10'000), ter(terNO_RIPPLE));
+
430 env.trust(USD1(30'000), alice);
+
431 env(pay(gw1, alice, USD1(30'000)));
+
432 AMM ammAlice1(
+
433 env, alice, USD(10'000), USD1(10'000), ter(terNO_RIPPLE));
+
434 }
+
435 }
+
436
+
437 void
+
438 testInvalidDeposit(FeatureBitset features)
+
439 {
+
440 testcase("Invalid Deposit");
+
441
+
442 using namespace jtx;
+
443
+
444 testAMM([&](AMM& ammAlice, Env& env) {
+
445 // Invalid flags
+
446 ammAlice.deposit(
+
447 alice,
+
448 1'000'000,
+
449 std::nullopt,
+
450 tfWithdrawAll,
+
451 ter(temINVALID_FLAG));
+
452
+
453 // Invalid options
+
454 std::vector<std::tuple<
455 std::optional<std::uint32_t>,
-
456 std::optional<STAmount>,
+
456 std::optional<std::uint32_t>,
457 std::optional<STAmount>,
458 std::optional<STAmount>,
-
459 std::optional<std::uint16_t>>>
-
460 invalidOptions = {
-
461 // flags, tokens, asset1In, asset2in, EPrice, tfee
-
462 {tfLPToken,
-
463 1'000,
-
464 std::nullopt,
-
465 USD(100),
-
466 std::nullopt,
-
467 std::nullopt},
-
468 {tfLPToken,
-
469 1'000,
-
470 XRP(100),
-
471 std::nullopt,
+
459 std::optional<STAmount>,
+
460 std::optional<std::uint16_t>>>
+
461 invalidOptions = {
+
462 // flags, tokens, asset1In, asset2in, EPrice, tfee
+
463 {tfLPToken,
+
464 1'000,
+
465 std::nullopt,
+
466 USD(100),
+
467 std::nullopt,
+
468 std::nullopt},
+
469 {tfLPToken,
+
470 1'000,
+
471 XRP(100),
472 std::nullopt,
-
473 std::nullopt},
-
474 {tfLPToken,
-
475 1'000,
-
476 std::nullopt,
+
473 std::nullopt,
+
474 std::nullopt},
+
475 {tfLPToken,
+
476 1'000,
477 std::nullopt,
-
478 STAmount{USD, 1, -1},
-
479 std::nullopt},
-
480 {tfLPToken,
-
481 std::nullopt,
-
482 USD(100),
-
483 std::nullopt,
-
484 STAmount{USD, 1, -1},
-
485 std::nullopt},
-
486 {tfLPToken,
-
487 1'000,
-
488 XRP(100),
-
489 std::nullopt,
-
490 STAmount{USD, 1, -1},
-
491 std::nullopt},
-
492 {tfLPToken,
-
493 1'000,
-
494 std::nullopt,
+
478 std::nullopt,
+
479 STAmount{USD, 1, -1},
+
480 std::nullopt},
+
481 {tfLPToken,
+
482 std::nullopt,
+
483 USD(100),
+
484 std::nullopt,
+
485 STAmount{USD, 1, -1},
+
486 std::nullopt},
+
487 {tfLPToken,
+
488 1'000,
+
489 XRP(100),
+
490 std::nullopt,
+
491 STAmount{USD, 1, -1},
+
492 std::nullopt},
+
493 {tfLPToken,
+
494 1'000,
495 std::nullopt,
496 std::nullopt,
-
497 1'000},
-
498 {tfSingleAsset,
-
499 1'000,
-
500 std::nullopt,
+
497 std::nullopt,
+
498 1'000},
+
499 {tfSingleAsset,
+
500 1'000,
501 std::nullopt,
502 std::nullopt,
-
503 std::nullopt},
-
504 {tfSingleAsset,
-
505 std::nullopt,
+
503 std::nullopt,
+
504 std::nullopt},
+
505 {tfSingleAsset,
506 std::nullopt,
-
507 USD(100),
-
508 std::nullopt,
-
509 std::nullopt},
-
510 {tfSingleAsset,
-
511 std::nullopt,
+
507 std::nullopt,
+
508 USD(100),
+
509 std::nullopt,
+
510 std::nullopt},
+
511 {tfSingleAsset,
512 std::nullopt,
513 std::nullopt,
-
514 STAmount{USD, 1, -1},
-
515 std::nullopt},
-
516 {tfSingleAsset,
-
517 std::nullopt,
-
518 USD(100),
-
519 std::nullopt,
+
514 std::nullopt,
+
515 STAmount{USD, 1, -1},
+
516 std::nullopt},
+
517 {tfSingleAsset,
+
518 std::nullopt,
+
519 USD(100),
520 std::nullopt,
-
521 1'000},
-
522 {tfTwoAsset,
-
523 1'000,
-
524 std::nullopt,
+
521 std::nullopt,
+
522 1'000},
+
523 {tfTwoAsset,
+
524 1'000,
525 std::nullopt,
526 std::nullopt,
-
527 std::nullopt},
-
528 {tfTwoAsset,
-
529 std::nullopt,
-
530 XRP(100),
-
531 USD(100),
-
532 STAmount{USD, 1, -1},
-
533 std::nullopt},
-
534 {tfTwoAsset,
-
535 std::nullopt,
-
536 XRP(100),
-
537 std::nullopt,
+
527 std::nullopt,
+
528 std::nullopt},
+
529 {tfTwoAsset,
+
530 std::nullopt,
+
531 XRP(100),
+
532 USD(100),
+
533 STAmount{USD, 1, -1},
+
534 std::nullopt},
+
535 {tfTwoAsset,
+
536 std::nullopt,
+
537 XRP(100),
538 std::nullopt,
-
539 std::nullopt},
-
540 {tfTwoAsset,
-
541 std::nullopt,
-
542 XRP(100),
-
543 USD(100),
-
544 std::nullopt,
-
545 1'000},
-
546 {tfTwoAsset,
-
547 std::nullopt,
+
539 std::nullopt,
+
540 std::nullopt},
+
541 {tfTwoAsset,
+
542 std::nullopt,
+
543 XRP(100),
+
544 USD(100),
+
545 std::nullopt,
+
546 1'000},
+
547 {tfTwoAsset,
548 std::nullopt,
-
549 USD(100),
-
550 STAmount{USD, 1, -1},
-
551 std::nullopt},
-
552 {tfOneAssetLPToken,
-
553 1'000,
-
554 std::nullopt,
+
549 std::nullopt,
+
550 USD(100),
+
551 STAmount{USD, 1, -1},
+
552 std::nullopt},
+
553 {tfOneAssetLPToken,
+
554 1'000,
555 std::nullopt,
556 std::nullopt,
-
557 std::nullopt},
-
558 {tfOneAssetLPToken,
-
559 std::nullopt,
-
560 XRP(100),
-
561 USD(100),
-
562 std::nullopt,
-
563 std::nullopt},
-
564 {tfOneAssetLPToken,
-
565 std::nullopt,
-
566 XRP(100),
-
567 std::nullopt,
-
568 STAmount{USD, 1, -1},
-
569 std::nullopt},
-
570 {tfOneAssetLPToken,
-
571 1'000,
-
572 XRP(100),
-
573 std::nullopt,
+
557 std::nullopt,
+
558 std::nullopt},
+
559 {tfOneAssetLPToken,
+
560 std::nullopt,
+
561 XRP(100),
+
562 USD(100),
+
563 std::nullopt,
+
564 std::nullopt},
+
565 {tfOneAssetLPToken,
+
566 std::nullopt,
+
567 XRP(100),
+
568 std::nullopt,
+
569 STAmount{USD, 1, -1},
+
570 std::nullopt},
+
571 {tfOneAssetLPToken,
+
572 1'000,
+
573 XRP(100),
574 std::nullopt,
-
575 1'000},
-
576 {tfLimitLPToken,
-
577 1'000,
-
578 std::nullopt,
+
575 std::nullopt,
+
576 1'000},
+
577 {tfLimitLPToken,
+
578 1'000,
579 std::nullopt,
580 std::nullopt,
-
581 std::nullopt},
-
582 {tfLimitLPToken,
-
583 1'000,
-
584 USD(100),
-
585 std::nullopt,
+
581 std::nullopt,
+
582 std::nullopt},
+
583 {tfLimitLPToken,
+
584 1'000,
+
585 USD(100),
586 std::nullopt,
-
587 std::nullopt},
-
588 {tfLimitLPToken,
-
589 std::nullopt,
-
590 USD(100),
-
591 XRP(100),
-
592 std::nullopt,
-
593 std::nullopt},
-
594 {tfLimitLPToken,
-
595 std::nullopt,
-
596 XRP(100),
-
597 std::nullopt,
-
598 STAmount{USD, 1, -1},
-
599 1'000},
-
600 {tfTwoAssetIfEmpty,
-
601 std::nullopt,
+
587 std::nullopt,
+
588 std::nullopt},
+
589 {tfLimitLPToken,
+
590 std::nullopt,
+
591 USD(100),
+
592 XRP(100),
+
593 std::nullopt,
+
594 std::nullopt},
+
595 {tfLimitLPToken,
+
596 std::nullopt,
+
597 XRP(100),
+
598 std::nullopt,
+
599 STAmount{USD, 1, -1},
+
600 1'000},
+
601 {tfTwoAssetIfEmpty,
602 std::nullopt,
603 std::nullopt,
604 std::nullopt,
-
605 1'000},
-
606 {tfTwoAssetIfEmpty,
-
607 1'000,
-
608 std::nullopt,
+
605 std::nullopt,
+
606 1'000},
+
607 {tfTwoAssetIfEmpty,
+
608 1'000,
609 std::nullopt,
610 std::nullopt,
-
611 std::nullopt},
-
612 {tfTwoAssetIfEmpty,
-
613 std::nullopt,
-
614 XRP(100),
-
615 USD(100),
-
616 STAmount{USD, 1, -1},
-
617 std::nullopt},
-
618 {tfTwoAssetIfEmpty | tfLPToken,
-
619 std::nullopt,
-
620 XRP(100),
-
621 USD(100),
-
622 STAmount{USD, 1, -1},
-
623 std::nullopt}};
-
624 for (auto const& it : invalidOptions)
-
625 {
-
626 ammAlice.deposit(
-
627 alice,
-
628 std::get<1>(it),
-
629 std::get<2>(it),
-
630 std::get<3>(it),
-
631 std::get<4>(it),
-
632 std::get<0>(it),
-
633 std::nullopt,
+
611 std::nullopt,
+
612 std::nullopt},
+
613 {tfTwoAssetIfEmpty,
+
614 std::nullopt,
+
615 XRP(100),
+
616 USD(100),
+
617 STAmount{USD, 1, -1},
+
618 std::nullopt},
+
619 {tfTwoAssetIfEmpty | tfLPToken,
+
620 std::nullopt,
+
621 XRP(100),
+
622 USD(100),
+
623 STAmount{USD, 1, -1},
+
624 std::nullopt}};
+
625 for (auto const& it : invalidOptions)
+
626 {
+
627 ammAlice.deposit(
+
628 alice,
+
629 std::get<1>(it),
+
630 std::get<2>(it),
+
631 std::get<3>(it),
+
632 std::get<4>(it),
+
633 std::get<0>(it),
634 std::nullopt,
-
635 std::get<5>(it),
-
636 ter(temMALFORMED));
-
637 }
-
638
-
639 {
-
640 // bad preflight1
-
641 Json::Value jv = Json::objectValue;
-
642 jv[jss::Account] = alice.human();
-
643 jv[jss::TransactionType] = jss::AMMDeposit;
-
644 jv[jss::Asset] =
-
645 STIssue(sfAsset, XRP).getJson(JsonOptions::none);
-
646 jv[jss::Asset2] =
-
647 STIssue(sfAsset, USD).getJson(JsonOptions::none);
-
648 jv[jss::Fee] = "-1";
-
649 env(jv, ter(temBAD_FEE));
-
650 }
-
651
-
652 // Invalid tokens
-
653 ammAlice.deposit(
-
654 alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
-
655 ammAlice.deposit(
-
656 alice,
-
657 IOUAmount{-1},
-
658 std::nullopt,
+
635 std::nullopt,
+
636 std::get<5>(it),
+
637 ter(temMALFORMED));
+
638 }
+
639
+
640 {
+
641 // bad preflight1
+
642 Json::Value jv = Json::objectValue;
+
643 jv[jss::Account] = alice.human();
+
644 jv[jss::TransactionType] = jss::AMMDeposit;
+
645 jv[jss::Asset] =
+
646 STIssue(sfAsset, XRP).getJson(JsonOptions::none);
+
647 jv[jss::Asset2] =
+
648 STIssue(sfAsset, USD).getJson(JsonOptions::none);
+
649 jv[jss::Fee] = "-1";
+
650 env(jv, ter(temBAD_FEE));
+
651 }
+
652
+
653 // Invalid tokens
+
654 ammAlice.deposit(
+
655 alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
+
656 ammAlice.deposit(
+
657 alice,
+
658 IOUAmount{-1},
659 std::nullopt,
-
660 ter(temBAD_AMM_TOKENS));
-
661
-
662 {
-
663 Json::Value jv = Json::objectValue;
-
664 jv[jss::Account] = alice.human();
-
665 jv[jss::TransactionType] = jss::AMMDeposit;
-
666 jv[jss::Asset] =
-
667 STIssue(sfAsset, XRP).getJson(JsonOptions::none);
-
668 jv[jss::Asset2] =
-
669 STIssue(sfAsset, USD).getJson(JsonOptions::none);
-
670 jv[jss::LPTokenOut] =
-
671 USD(100).value().getJson(JsonOptions::none);
-
672 jv[jss::Flags] = tfLPToken;
-
673 env(jv, ter(temBAD_AMM_TOKENS));
-
674 }
-
675
-
676 // Invalid trading fee
-
677 ammAlice.deposit(
-
678 carol,
-
679 std::nullopt,
-
680 XRP(200),
-
681 USD(200),
-
682 std::nullopt,
-
683 tfTwoAssetIfEmpty,
-
684 std::nullopt,
+
660 std::nullopt,
+
661 ter(temBAD_AMM_TOKENS));
+
662
+
663 {
+
664 Json::Value jv = Json::objectValue;
+
665 jv[jss::Account] = alice.human();
+
666 jv[jss::TransactionType] = jss::AMMDeposit;
+
667 jv[jss::Asset] =
+
668 STIssue(sfAsset, XRP).getJson(JsonOptions::none);
+
669 jv[jss::Asset2] =
+
670 STIssue(sfAsset, USD).getJson(JsonOptions::none);
+
671 jv[jss::LPTokenOut] =
+
672 USD(100).value().getJson(JsonOptions::none);
+
673 jv[jss::Flags] = tfLPToken;
+
674 env(jv, ter(temBAD_AMM_TOKENS));
+
675 }
+
676
+
677 // Invalid trading fee
+
678 ammAlice.deposit(
+
679 carol,
+
680 std::nullopt,
+
681 XRP(200),
+
682 USD(200),
+
683 std::nullopt,
+
684 tfTwoAssetIfEmpty,
685 std::nullopt,
-
686 10'000,
-
687 ter(temBAD_FEE));
-
688
-
689 // Invalid tokens - bogus currency
-
690 {
-
691 auto const iss1 = Issue{Currency(0xabc), gw.id()};
-
692 auto const iss2 = Issue{Currency(0xdef), gw.id()};
-
693 ammAlice.deposit(
-
694 alice,
-
695 1'000,
-
696 std::nullopt,
+
686 std::nullopt,
+
687 10'000,
+
688 ter(temBAD_FEE));
+
689
+
690 // Invalid tokens - bogus currency
+
691 {
+
692 auto const iss1 = Issue{Currency(0xabc), gw.id()};
+
693 auto const iss2 = Issue{Currency(0xdef), gw.id()};
+
694 ammAlice.deposit(
+
695 alice,
+
696 1'000,
697 std::nullopt,
698 std::nullopt,
699 std::nullopt,
-
700 {{iss1, iss2}},
-
701 std::nullopt,
+
700 std::nullopt,
+
701 {{iss1, iss2}},
702 std::nullopt,
-
703 ter(terNO_AMM));
-
704 }
-
705
-
706 // Depositing mismatched token, invalid Asset1In.issue
-
707 ammAlice.deposit(
-
708 alice,
-
709 GBP(100),
-
710 std::nullopt,
+
703 std::nullopt,
+
704 ter(terNO_AMM));
+
705 }
+
706
+
707 // Depositing mismatched token, invalid Asset1In.issue
+
708 ammAlice.deposit(
+
709 alice,
+
710 GBP(100),
711 std::nullopt,
712 std::nullopt,
-
713 ter(temBAD_AMM_TOKENS));
-
714
-
715 // Depositing mismatched token, invalid Asset2In.issue
-
716 ammAlice.deposit(
-
717 alice,
-
718 USD(100),
-
719 GBP(100),
-
720 std::nullopt,
+
713 std::nullopt,
+
714 ter(temBAD_AMM_TOKENS));
+
715
+
716 // Depositing mismatched token, invalid Asset2In.issue
+
717 ammAlice.deposit(
+
718 alice,
+
719 USD(100),
+
720 GBP(100),
721 std::nullopt,
-
722 ter(temBAD_AMM_TOKENS));
-
723
-
724 // Depositing mismatched token, Asset1In.issue == Asset2In.issue
-
725 ammAlice.deposit(
-
726 alice,
-
727 USD(100),
+
722 std::nullopt,
+
723 ter(temBAD_AMM_TOKENS));
+
724
+
725 // Depositing mismatched token, Asset1In.issue == Asset2In.issue
+
726 ammAlice.deposit(
+
727 alice,
728 USD(100),
-
729 std::nullopt,
+
729 USD(100),
730 std::nullopt,
-
731 ter(temBAD_AMM_TOKENS));
-
732
-
733 // Invalid amount value
-
734 ammAlice.deposit(
-
735 alice,
-
736 USD(0),
-
737 std::nullopt,
+
731 std::nullopt,
+
732 ter(temBAD_AMM_TOKENS));
+
733
+
734 // Invalid amount value
+
735 ammAlice.deposit(
+
736 alice,
+
737 USD(0),
738 std::nullopt,
739 std::nullopt,
-
740 ter(temBAD_AMOUNT));
-
741 ammAlice.deposit(
-
742 alice,
-
743 USD(-1'000),
-
744 std::nullopt,
+
740 std::nullopt,
+
741 ter(temBAD_AMOUNT));
+
742 ammAlice.deposit(
+
743 alice,
+
744 USD(-1'000),
745 std::nullopt,
746 std::nullopt,
-
747 ter(temBAD_AMOUNT));
-
748 ammAlice.deposit(
-
749 alice,
-
750 USD(10),
-
751 std::nullopt,
-
752 USD(-1),
-
753 std::nullopt,
-
754 ter(temBAD_AMOUNT));
-
755
-
756 // Bad currency
-
757 ammAlice.deposit(
-
758 alice,
-
759 BAD(100),
-
760 std::nullopt,
+
747 std::nullopt,
+
748 ter(temBAD_AMOUNT));
+
749 ammAlice.deposit(
+
750 alice,
+
751 USD(10),
+
752 std::nullopt,
+
753 USD(-1),
+
754 std::nullopt,
+
755 ter(temBAD_AMOUNT));
+
756
+
757 // Bad currency
+
758 ammAlice.deposit(
+
759 alice,
+
760 BAD(100),
761 std::nullopt,
762 std::nullopt,
-
763 ter(temBAD_CURRENCY));
-
764
-
765 // Invalid Account
-
766 Account bad("bad");
-
767 env.memoize(bad);
-
768 ammAlice.deposit(
-
769 bad,
-
770 1'000'000,
-
771 std::nullopt,
+
763 std::nullopt,
+
764 ter(temBAD_CURRENCY));
+
765
+
766 // Invalid Account
+
767 Account bad("bad");
+
768 env.memoize(bad);
+
769 ammAlice.deposit(
+
770 bad,
+
771 1'000'000,
772 std::nullopt,
773 std::nullopt,
774 std::nullopt,
775 std::nullopt,
-
776 seq(1),
-
777 std::nullopt,
-
778 ter(terNO_ACCOUNT));
-
779
-
780 // Invalid AMM
-
781 ammAlice.deposit(
-
782 alice,
-
783 1'000,
-
784 std::nullopt,
+
776 std::nullopt,
+
777 seq(1),
+
778 std::nullopt,
+
779 ter(terNO_ACCOUNT));
+
780
+
781 // Invalid AMM
+
782 ammAlice.deposit(
+
783 alice,
+
784 1'000,
785 std::nullopt,
786 std::nullopt,
787 std::nullopt,
-
788 {{USD, GBP}},
-
789 std::nullopt,
+
788 std::nullopt,
+
789 {{USD, GBP}},
790 std::nullopt,
-
791 ter(terNO_AMM));
-
792
-
793 // Single deposit: 100000 tokens worth of USD
-
794 // Amount to deposit exceeds Max
-
795 ammAlice.deposit(
-
796 carol,
-
797 100'000,
-
798 USD(200),
-
799 std::nullopt,
+
791 std::nullopt,
+
792 ter(terNO_AMM));
+
793
+
794 // Single deposit: 100000 tokens worth of USD
+
795 // Amount to deposit exceeds Max
+
796 ammAlice.deposit(
+
797 carol,
+
798 100'000,
+
799 USD(200),
800 std::nullopt,
801 std::nullopt,
802 std::nullopt,
803 std::nullopt,
804 std::nullopt,
-
805 ter(tecAMM_FAILED));
-
806
-
807 // Single deposit: 100000 tokens worth of XRP
-
808 // Amount to deposit exceeds Max
-
809 ammAlice.deposit(
-
810 carol,
-
811 100'000,
-
812 XRP(200),
-
813 std::nullopt,
+
805 std::nullopt,
+
806 ter(tecAMM_FAILED));
+
807
+
808 // Single deposit: 100000 tokens worth of XRP
+
809 // Amount to deposit exceeds Max
+
810 ammAlice.deposit(
+
811 carol,
+
812 100'000,
+
813 XRP(200),
814 std::nullopt,
815 std::nullopt,
816 std::nullopt,
817 std::nullopt,
818 std::nullopt,
-
819 ter(tecAMM_FAILED));
-
820
-
821 // Deposit amount is invalid
-
822 // Calculated amount to deposit is 98,000,000
-
823 ammAlice.deposit(
-
824 alice,
-
825 USD(0),
-
826 std::nullopt,
-
827 STAmount{USD, 1, -1},
-
828 std::nullopt,
-
829 ter(tecUNFUNDED_AMM));
-
830 // Calculated amount is 0
-
831 ammAlice.deposit(
-
832 alice,
-
833 USD(0),
-
834 std::nullopt,
-
835 STAmount{USD, 2'000, -6},
-
836 std::nullopt,
-
837 ter(tecAMM_FAILED));
-
838
-
839 // Tiny deposit
-
840 ammAlice.deposit(
-
841 carol,
-
842 IOUAmount{1, -4},
-
843 std::nullopt,
-
844 std::nullopt,
-
845 ter(temBAD_AMOUNT));
-
846 ammAlice.deposit(
-
847 carol,
-
848 STAmount{USD, 1, -12},
-
849 std::nullopt,
-
850 std::nullopt,
-
851 std::nullopt,
-
852 ter(tecAMM_INVALID_TOKENS));
-
853
-
854 // Deposit non-empty AMM
-
855 ammAlice.deposit(
-
856 carol,
-
857 XRP(100),
-
858 USD(100),
-
859 std::nullopt,
-
860 tfTwoAssetIfEmpty,
-
861 ter(tecAMM_NOT_EMPTY));
-
862 });
-
863
-
864 // Invalid AMM
-
865 testAMM([&](AMM& ammAlice, Env& env) {
-
866 ammAlice.withdrawAll(alice);
-
867 ammAlice.deposit(
-
868 alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM));
-
869 });
-
870
-
871 // Globally frozen asset
-
872 testAMM(
-
873 [&](AMM& ammAlice, Env& env) {
-
874 env(fset(gw, asfGlobalFreeze));
-
875 if (!features[featureAMMClawback])
-
876 // If the issuer set global freeze, the holder still can
-
877 // deposit the other non-frozen token when AMMClawback is
-
878 // not enabled.
-
879 ammAlice.deposit(carol, XRP(100));
-
880 else
-
881 // If the issuer set global freeze, the holder cannot
-
882 // deposit the other non-frozen token when AMMClawback is
-
883 // enabled.
-
884 ammAlice.deposit(
-
885 carol,
-
886 XRP(100),
-
887 std::nullopt,
-
888 std::nullopt,
-
889 std::nullopt,
-
890 ter(tecFROZEN));
-
891 ammAlice.deposit(
-
892 carol,
-
893 USD(100),
-
894 std::nullopt,
-
895 std::nullopt,
-
896 std::nullopt,
-
897 ter(tecFROZEN));
-
898 ammAlice.deposit(
-
899 carol,
-
900 1'000'000,
-
901 std::nullopt,
-
902 std::nullopt,
-
903 ter(tecFROZEN));
-
904 ammAlice.deposit(
-
905 carol,
-
906 XRP(100),
+
819 std::nullopt,
+
820 ter(tecAMM_FAILED));
+
821
+
822 // Deposit amount is invalid
+
823 // Calculated amount to deposit is 98,000,000
+
824 ammAlice.deposit(
+
825 alice,
+
826 USD(0),
+
827 std::nullopt,
+
828 STAmount{USD, 1, -1},
+
829 std::nullopt,
+
830 ter(tecUNFUNDED_AMM));
+
831 // Calculated amount is 0
+
832 ammAlice.deposit(
+
833 alice,
+
834 USD(0),
+
835 std::nullopt,
+
836 STAmount{USD, 2'000, -6},
+
837 std::nullopt,
+
838 ter(tecAMM_FAILED));
+
839
+
840 // Deposit non-empty AMM
+
841 ammAlice.deposit(
+
842 carol,
+
843 XRP(100),
+
844 USD(100),
+
845 std::nullopt,
+
846 tfTwoAssetIfEmpty,
+
847 ter(tecAMM_NOT_EMPTY));
+
848 });
+
849
+
850 // Tiny deposit
+
851 testAMM(
+
852 [&](AMM& ammAlice, Env& env) {
+
853 auto const enabledv1_3 =
+
854 env.current()->rules().enabled(fixAMMv1_3);
+
855 auto const err =
+
856 !enabledv1_3 ? ter(temBAD_AMOUNT) : ter(tesSUCCESS);
+
857 // Pre-amendment XRP deposit side is rounded to 0
+
858 // and deposit fails.
+
859 // Post-amendment XRP deposit side is rounded to 1
+
860 // and deposit succeeds.
+
861 ammAlice.deposit(
+
862 carol, IOUAmount{1, -4}, std::nullopt, std::nullopt, err);
+
863 // Pre/post-amendment LPTokens is rounded to 0 and deposit
+
864 // fails with tecAMM_INVALID_TOKENS.
+
865 ammAlice.deposit(
+
866 carol,
+
867 STAmount{USD, 1, -12},
+
868 std::nullopt,
+
869 std::nullopt,
+
870 std::nullopt,
+
871 ter(tecAMM_INVALID_TOKENS));
+
872 },
+
873 std::nullopt,
+
874 0,
+
875 std::nullopt,
+
876 {features, features - fixAMMv1_3});
+
877
+
878 // Invalid AMM
+
879 testAMM([&](AMM& ammAlice, Env& env) {
+
880 ammAlice.withdrawAll(alice);
+
881 ammAlice.deposit(
+
882 alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM));
+
883 });
+
884
+
885 // Globally frozen asset
+
886 testAMM(
+
887 [&](AMM& ammAlice, Env& env) {
+
888 env(fset(gw, asfGlobalFreeze));
+
889 if (!features[featureAMMClawback])
+
890 // If the issuer set global freeze, the holder still can
+
891 // deposit the other non-frozen token when AMMClawback is
+
892 // not enabled.
+
893 ammAlice.deposit(carol, XRP(100));
+
894 else
+
895 // If the issuer set global freeze, the holder cannot
+
896 // deposit the other non-frozen token when AMMClawback is
+
897 // enabled.
+
898 ammAlice.deposit(
+
899 carol,
+
900 XRP(100),
+
901 std::nullopt,
+
902 std::nullopt,
+
903 std::nullopt,
+
904 ter(tecFROZEN));
+
905 ammAlice.deposit(
+
906 carol,
907 USD(100),
908 std::nullopt,
909 std::nullopt,
-
910 ter(tecFROZEN));
-
911 },
-
912 std::nullopt,
-
913 0,
-
914 std::nullopt,
-
915 {features});
-
916
-
917 // Individually frozen (AMM) account
-
918 testAMM(
-
919 [&](AMM& ammAlice, Env& env) {
-
920 env(trust(gw, carol["USD"](0), tfSetFreeze));
-
921 env.close();
-
922 if (!features[featureAMMClawback])
-
923 // Can deposit non-frozen token if AMMClawback is not
-
924 // enabled
-
925 ammAlice.deposit(carol, XRP(100));
-
926 else
-
927 // Cannot deposit non-frozen token if the other token is
-
928 // frozen when AMMClawback is enabled
-
929 ammAlice.deposit(
-
930 carol,
-
931 XRP(100),
-
932 std::nullopt,
-
933 std::nullopt,
-
934 std::nullopt,
-
935 ter(tecFROZEN));
-
936
-
937 ammAlice.deposit(
-
938 carol,
-
939 1'000'000,
-
940 std::nullopt,
-
941 std::nullopt,
-
942 ter(tecFROZEN));
-
943 ammAlice.deposit(
-
944 carol,
-
945 USD(100),
-
946 std::nullopt,
-
947 std::nullopt,
-
948 std::nullopt,
-
949 ter(tecFROZEN));
-
950 env(trust(gw, carol["USD"](0), tfClearFreeze));
-
951 // Individually frozen AMM
-
952 env(trust(
-
953 gw,
-
954 STAmount{
-
955 Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
-
956 tfSetFreeze));
-
957 env.close();
-
958 // Can deposit non-frozen token
-
959 ammAlice.deposit(carol, XRP(100));
-
960 ammAlice.deposit(
-
961 carol,
-
962 1'000'000,
-
963 std::nullopt,
-
964 std::nullopt,
-
965 ter(tecFROZEN));
-
966 ammAlice.deposit(
-
967 carol,
-
968 USD(100),
-
969 std::nullopt,
-
970 std::nullopt,
-
971 std::nullopt,
-
972 ter(tecFROZEN));
-
973 },
-
974 std::nullopt,
-
975 0,
-
976 std::nullopt,
-
977 {features});
-
978
-
979 // Individually frozen (AMM) account with IOU/IOU AMM
-
980 testAMM(
-
981 [&](AMM& ammAlice, Env& env) {
-
982 env(trust(gw, carol["USD"](0), tfSetFreeze));
-
983 env(trust(gw, carol["BTC"](0), tfSetFreeze));
-
984 env.close();
-
985 ammAlice.deposit(
-
986 carol,
-
987 1'000'000,
-
988 std::nullopt,
-
989 std::nullopt,
-
990 ter(tecFROZEN));
-
991 ammAlice.deposit(
-
992 carol,
-
993 USD(100),
-
994 std::nullopt,
-
995 std::nullopt,
-
996 std::nullopt,
-
997 ter(tecFROZEN));
-
998 env(trust(gw, carol["USD"](0), tfClearFreeze));
-
999 // Individually frozen AMM
-
1000 env(trust(
-
1001 gw,
-
1002 STAmount{
-
1003 Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
-
1004 tfSetFreeze));
-
1005 env.close();
-
1006 // Cannot deposit non-frozen token
-
1007 ammAlice.deposit(
-
1008 carol,
-
1009 1'000'000,
+
910 std::nullopt,
+
911 ter(tecFROZEN));
+
912 ammAlice.deposit(
+
913 carol,
+
914 1'000'000,
+
915 std::nullopt,
+
916 std::nullopt,
+
917 ter(tecFROZEN));
+
918 ammAlice.deposit(
+
919 carol,
+
920 XRP(100),
+
921 USD(100),
+
922 std::nullopt,
+
923 std::nullopt,
+
924 ter(tecFROZEN));
+
925 },
+
926 std::nullopt,
+
927 0,
+
928 std::nullopt,
+
929 {features});
+
930
+
931 // Individually frozen (AMM) account
+
932 testAMM(
+
933 [&](AMM& ammAlice, Env& env) {
+
934 env(trust(gw, carol["USD"](0), tfSetFreeze));
+
935 env.close();
+
936 if (!features[featureAMMClawback])
+
937 // Can deposit non-frozen token if AMMClawback is not
+
938 // enabled
+
939 ammAlice.deposit(carol, XRP(100));
+
940 else
+
941 // Cannot deposit non-frozen token if the other token is
+
942 // frozen when AMMClawback is enabled
+
943 ammAlice.deposit(
+
944 carol,
+
945 XRP(100),
+
946 std::nullopt,
+
947 std::nullopt,
+
948 std::nullopt,
+
949 ter(tecFROZEN));
+
950
+
951 ammAlice.deposit(
+
952 carol,
+
953 1'000'000,
+
954 std::nullopt,
+
955 std::nullopt,
+
956 ter(tecFROZEN));
+
957 ammAlice.deposit(
+
958 carol,
+
959 USD(100),
+
960 std::nullopt,
+
961 std::nullopt,
+
962 std::nullopt,
+
963 ter(tecFROZEN));
+
964 env(trust(gw, carol["USD"](0), tfClearFreeze));
+
965 // Individually frozen AMM
+
966 env(trust(
+
967 gw,
+
968 STAmount{
+
969 Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
+
970 tfSetFreeze));
+
971 env.close();
+
972 // Can deposit non-frozen token
+
973 ammAlice.deposit(carol, XRP(100));
+
974 ammAlice.deposit(
+
975 carol,
+
976 1'000'000,
+
977 std::nullopt,
+
978 std::nullopt,
+
979 ter(tecFROZEN));
+
980 ammAlice.deposit(
+
981 carol,
+
982 USD(100),
+
983 std::nullopt,
+
984 std::nullopt,
+
985 std::nullopt,
+
986 ter(tecFROZEN));
+
987 },
+
988 std::nullopt,
+
989 0,
+
990 std::nullopt,
+
991 {features});
+
992
+
993 // Individually frozen (AMM) account with IOU/IOU AMM
+
994 testAMM(
+
995 [&](AMM& ammAlice, Env& env) {
+
996 env(trust(gw, carol["USD"](0), tfSetFreeze));
+
997 env(trust(gw, carol["BTC"](0), tfSetFreeze));
+
998 env.close();
+
999 ammAlice.deposit(
+
1000 carol,
+
1001 1'000'000,
+
1002 std::nullopt,
+
1003 std::nullopt,
+
1004 ter(tecFROZEN));
+
1005 ammAlice.deposit(
+
1006 carol,
+
1007 USD(100),
+
1008 std::nullopt,
+
1009 std::nullopt,
1010 std::nullopt,
-
1011 std::nullopt,
-
1012 ter(tecFROZEN));
-
1013 ammAlice.deposit(
-
1014 carol,
-
1015 USD(100),
-
1016 BTC(0.01),
-
1017 std::nullopt,
-
1018 std::nullopt,
-
1019 ter(tecFROZEN));
-
1020 },
-
1021 {{USD(20'000), BTC(0.5)}});
-
1022
-
1023 // Deposit unauthorized token.
-
1024 {
-
1025 Env env(*this, features);
-
1026 env.fund(XRP(1000), gw, alice, bob);
-
1027 env(fset(gw, asfRequireAuth));
-
1028 env.close();
-
1029 env(trust(gw, alice["USD"](100)), txflags(tfSetfAuth));
-
1030 env(trust(alice, gw["USD"](20)));
-
1031 env.close();
-
1032 env(pay(gw, alice, gw["USD"](10)));
-
1033 env.close();
-
1034 env(trust(gw, bob["USD"](100)));
-
1035 env.close();
+
1011 ter(tecFROZEN));
+
1012 env(trust(gw, carol["USD"](0), tfClearFreeze));
+
1013 // Individually frozen AMM
+
1014 env(trust(
+
1015 gw,
+
1016 STAmount{
+
1017 Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
+
1018 tfSetFreeze));
+
1019 env.close();
+
1020 // Cannot deposit non-frozen token
+
1021 ammAlice.deposit(
+
1022 carol,
+
1023 1'000'000,
+
1024 std::nullopt,
+
1025 std::nullopt,
+
1026 ter(tecFROZEN));
+
1027 ammAlice.deposit(
+
1028 carol,
+
1029 USD(100),
+
1030 BTC(0.01),
+
1031 std::nullopt,
+
1032 std::nullopt,
+
1033 ter(tecFROZEN));
+
1034 },
+
1035 {{USD(20'000), BTC(0.5)}});
1036
-
1037 AMM amm(env, alice, XRP(10), gw["USD"](10), ter(tesSUCCESS));
-
1038 env.close();
-
1039
-
1040 if (features[featureAMMClawback])
-
1041 // if featureAMMClawback is enabled, bob can not deposit XRP
-
1042 // because he's not authorized to hold the paired token
-
1043 // gw["USD"].
-
1044 amm.deposit(
-
1045 bob,
-
1046 XRP(10),
-
1047 std::nullopt,
-
1048 std::nullopt,
-
1049 std::nullopt,
-
1050 ter(tecNO_AUTH));
-
1051 else
-
1052 amm.deposit(
-
1053 bob,
-
1054 XRP(10),
-
1055 std::nullopt,
-
1056 std::nullopt,
-
1057 std::nullopt,
-
1058 ter(tesSUCCESS));
-
1059 }
-
1060
-
1061 // Insufficient XRP balance
-
1062 testAMM([&](AMM& ammAlice, Env& env) {
-
1063 env.fund(XRP(1'000), bob);
-
1064 env.close();
-
1065 // Adds LPT trustline
-
1066 ammAlice.deposit(bob, XRP(10));
-
1067 ammAlice.deposit(
-
1068 bob,
-
1069 XRP(1'000),
-
1070 std::nullopt,
-
1071 std::nullopt,
-
1072 std::nullopt,
-
1073 ter(tecUNFUNDED_AMM));
-
1074 });
-
1075
-
1076 // Insufficient USD balance
-
1077 testAMM([&](AMM& ammAlice, Env& env) {
-
1078 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
-
1079 env.close();
-
1080 ammAlice.deposit(
-
1081 bob,
-
1082 USD(1'001),
-
1083 std::nullopt,
+
1037 // Deposit unauthorized token.
+
1038 {
+
1039 Env env(*this, features);
+
1040 env.fund(XRP(1000), gw, alice, bob);
+
1041 env(fset(gw, asfRequireAuth));
+
1042 env.close();
+
1043 env(trust(gw, alice["USD"](100)), txflags(tfSetfAuth));
+
1044 env(trust(alice, gw["USD"](20)));
+
1045 env.close();
+
1046 env(pay(gw, alice, gw["USD"](10)));
+
1047 env.close();
+
1048 env(trust(gw, bob["USD"](100)));
+
1049 env.close();
+
1050
+
1051 AMM amm(env, alice, XRP(10), gw["USD"](10), ter(tesSUCCESS));
+
1052 env.close();
+
1053
+
1054 if (features[featureAMMClawback])
+
1055 // if featureAMMClawback is enabled, bob can not deposit XRP
+
1056 // because he's not authorized to hold the paired token
+
1057 // gw["USD"].
+
1058 amm.deposit(
+
1059 bob,
+
1060 XRP(10),
+
1061 std::nullopt,
+
1062 std::nullopt,
+
1063 std::nullopt,
+
1064 ter(tecNO_AUTH));
+
1065 else
+
1066 amm.deposit(
+
1067 bob,
+
1068 XRP(10),
+
1069 std::nullopt,
+
1070 std::nullopt,
+
1071 std::nullopt,
+
1072 ter(tesSUCCESS));
+
1073 }
+
1074
+
1075 // Insufficient XRP balance
+
1076 testAMM([&](AMM& ammAlice, Env& env) {
+
1077 env.fund(XRP(1'000), bob);
+
1078 env.close();
+
1079 // Adds LPT trustline
+
1080 ammAlice.deposit(bob, XRP(10));
+
1081 ammAlice.deposit(
+
1082 bob,
+
1083 XRP(1'000),
1084 std::nullopt,
1085 std::nullopt,
-
1086 ter(tecUNFUNDED_AMM));
-
1087 });
-
1088
-
1089 // Insufficient USD balance by tokens
-
1090 testAMM([&](AMM& ammAlice, Env& env) {
-
1091 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
-
1092 env.close();
-
1093 ammAlice.deposit(
-
1094 bob,
-
1095 10'000'000,
-
1096 std::nullopt,
+
1086 std::nullopt,
+
1087 ter(tecUNFUNDED_AMM));
+
1088 });
+
1089
+
1090 // Insufficient USD balance
+
1091 testAMM([&](AMM& ammAlice, Env& env) {
+
1092 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
+
1093 env.close();
+
1094 ammAlice.deposit(
+
1095 bob,
+
1096 USD(1'001),
1097 std::nullopt,
1098 std::nullopt,
1099 std::nullopt,
-
1100 std::nullopt,
-
1101 std::nullopt,
-
1102 std::nullopt,
-
1103 ter(tecUNFUNDED_AMM));
-
1104 });
-
1105
-
1106 // Insufficient XRP balance by tokens
-
1107 testAMM([&](AMM& ammAlice, Env& env) {
-
1108 env.fund(XRP(1'000), bob);
-
1109 env.trust(USD(100'000), bob);
-
1110 env.close();
-
1111 env(pay(gw, bob, USD(90'000)));
-
1112 env.close();
-
1113 ammAlice.deposit(
-
1114 bob,
-
1115 10'000'000,
+
1100 ter(tecUNFUNDED_AMM));
+
1101 });
+
1102
+
1103 // Insufficient USD balance by tokens
+
1104 testAMM([&](AMM& ammAlice, Env& env) {
+
1105 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
+
1106 env.close();
+
1107 ammAlice.deposit(
+
1108 bob,
+
1109 10'000'000,
+
1110 std::nullopt,
+
1111 std::nullopt,
+
1112 std::nullopt,
+
1113 std::nullopt,
+
1114 std::nullopt,
+
1115 std::nullopt,
1116 std::nullopt,
-
1117 std::nullopt,
-
1118 std::nullopt,
-
1119 std::nullopt,
-
1120 std::nullopt,
-
1121 std::nullopt,
-
1122 std::nullopt,
-
1123 ter(tecUNFUNDED_AMM));
-
1124 });
-
1125
-
1126 // Insufficient reserve, XRP/IOU
-
1127 {
-
1128 Env env(*this);
-
1129 auto const starting_xrp =
-
1130 reserve(env, 4) + env.current()->fees().base * 4;
-
1131 env.fund(XRP(10'000), gw);
-
1132 env.fund(XRP(10'000), alice);
-
1133 env.fund(starting_xrp, carol);
-
1134 env.trust(USD(2'000), alice);
-
1135 env.trust(USD(2'000), carol);
-
1136 env.close();
-
1137 env(pay(gw, alice, USD(2'000)));
-
1138 env(pay(gw, carol, USD(2'000)));
-
1139 env.close();
-
1140 env(offer(carol, XRP(100), USD(101)));
-
1141 env(offer(carol, XRP(100), USD(102)));
-
1142 AMM ammAlice(env, alice, XRP(1'000), USD(1'000));
-
1143 ammAlice.deposit(
-
1144 carol,
-
1145 XRP(100),
-
1146 std::nullopt,
-
1147 std::nullopt,
-
1148 std::nullopt,
-
1149 ter(tecINSUF_RESERVE_LINE));
-
1150
-
1151 env(offer(carol, XRP(100), USD(103)));
-
1152 ammAlice.deposit(
-
1153 carol,
-
1154 USD(100),
-
1155 std::nullopt,
-
1156 std::nullopt,
-
1157 std::nullopt,
-
1158 ter(tecINSUF_RESERVE_LINE));
-
1159 }
-
1160
-
1161 // Insufficient reserve, IOU/IOU
-
1162 {
-
1163 Env env(*this);
-
1164 auto const starting_xrp =
-
1165 reserve(env, 4) + env.current()->fees().base * 4;
-
1166 env.fund(XRP(10'000), gw);
-
1167 env.fund(XRP(10'000), alice);
-
1168 env.fund(starting_xrp, carol);
-
1169 env.trust(USD(2'000), alice);
-
1170 env.trust(EUR(2'000), alice);
-
1171 env.trust(USD(2'000), carol);
-
1172 env.trust(EUR(2'000), carol);
-
1173 env.close();
-
1174 env(pay(gw, alice, USD(2'000)));
-
1175 env(pay(gw, alice, EUR(2'000)));
-
1176 env(pay(gw, carol, USD(2'000)));
-
1177 env(pay(gw, carol, EUR(2'000)));
-
1178 env.close();
-
1179 env(offer(carol, XRP(100), USD(101)));
-
1180 env(offer(carol, XRP(100), USD(102)));
-
1181 AMM ammAlice(env, alice, XRP(1'000), USD(1'000));
-
1182 ammAlice.deposit(
-
1183 carol,
-
1184 XRP(100),
-
1185 std::nullopt,
-
1186 std::nullopt,
-
1187 std::nullopt,
-
1188 ter(tecINSUF_RESERVE_LINE));
-
1189 }
-
1190
-
1191 // Invalid min
-
1192 testAMM([&](AMM& ammAlice, Env& env) {
-
1193 // min tokens can't be <= zero
-
1194 ammAlice.deposit(
-
1195 carol, 0, XRP(100), tfSingleAsset, ter(temBAD_AMM_TOKENS));
+
1117 ter(tecUNFUNDED_AMM));
+
1118 });
+
1119
+
1120 // Insufficient XRP balance by tokens
+
1121 testAMM([&](AMM& ammAlice, Env& env) {
+
1122 env.fund(XRP(1'000), bob);
+
1123 env.trust(USD(100'000), bob);
+
1124 env.close();
+
1125 env(pay(gw, bob, USD(90'000)));
+
1126 env.close();
+
1127 ammAlice.deposit(
+
1128 bob,
+
1129 10'000'000,
+
1130 std::nullopt,
+
1131 std::nullopt,
+
1132 std::nullopt,
+
1133 std::nullopt,
+
1134 std::nullopt,
+
1135 std::nullopt,
+
1136 std::nullopt,
+
1137 ter(tecUNFUNDED_AMM));
+
1138 });
+
1139
+
1140 // Insufficient reserve, XRP/IOU
+
1141 {
+
1142 Env env(*this);
+
1143 auto const starting_xrp =
+
1144 reserve(env, 4) + env.current()->fees().base * 4;
+
1145 env.fund(XRP(10'000), gw);
+
1146 env.fund(XRP(10'000), alice);
+
1147 env.fund(starting_xrp, carol);
+
1148 env.trust(USD(2'000), alice);
+
1149 env.trust(USD(2'000), carol);
+
1150 env.close();
+
1151 env(pay(gw, alice, USD(2'000)));
+
1152 env(pay(gw, carol, USD(2'000)));
+
1153 env.close();
+
1154 env(offer(carol, XRP(100), USD(101)));
+
1155 env(offer(carol, XRP(100), USD(102)));
+
1156 AMM ammAlice(env, alice, XRP(1'000), USD(1'000));
+
1157 ammAlice.deposit(
+
1158 carol,
+
1159 XRP(100),
+
1160 std::nullopt,
+
1161 std::nullopt,
+
1162 std::nullopt,
+
1163 ter(tecINSUF_RESERVE_LINE));
+
1164
+
1165 env(offer(carol, XRP(100), USD(103)));
+
1166 ammAlice.deposit(
+
1167 carol,
+
1168 USD(100),
+
1169 std::nullopt,
+
1170 std::nullopt,
+
1171 std::nullopt,
+
1172 ter(tecINSUF_RESERVE_LINE));
+
1173 }
+
1174
+
1175 // Insufficient reserve, IOU/IOU
+
1176 {
+
1177 Env env(*this);
+
1178 auto const starting_xrp =
+
1179 reserve(env, 4) + env.current()->fees().base * 4;
+
1180 env.fund(XRP(10'000), gw);
+
1181 env.fund(XRP(10'000), alice);
+
1182 env.fund(starting_xrp, carol);
+
1183 env.trust(USD(2'000), alice);
+
1184 env.trust(EUR(2'000), alice);
+
1185 env.trust(USD(2'000), carol);
+
1186 env.trust(EUR(2'000), carol);
+
1187 env.close();
+
1188 env(pay(gw, alice, USD(2'000)));
+
1189 env(pay(gw, alice, EUR(2'000)));
+
1190 env(pay(gw, carol, USD(2'000)));
+
1191 env(pay(gw, carol, EUR(2'000)));
+
1192 env.close();
+
1193 env(offer(carol, XRP(100), USD(101)));
+
1194 env(offer(carol, XRP(100), USD(102)));
+
1195 AMM ammAlice(env, alice, XRP(1'000), USD(1'000));
1196 ammAlice.deposit(
-
1197 carol, -1, XRP(100), tfSingleAsset, ter(temBAD_AMM_TOKENS));
-
1198 ammAlice.deposit(
-
1199 carol,
-
1200 0,
-
1201 XRP(100),
-
1202 USD(100),
-
1203 std::nullopt,
-
1204 tfTwoAsset,
-
1205 std::nullopt,
-
1206 std::nullopt,
-
1207 std::nullopt,
-
1208 ter(temBAD_AMM_TOKENS));
-
1209 // min amounts can't be <= zero
+
1197 carol,
+
1198 XRP(100),
+
1199 std::nullopt,
+
1200 std::nullopt,
+
1201 std::nullopt,
+
1202 ter(tecINSUF_RESERVE_LINE));
+
1203 }
+
1204
+
1205 // Invalid min
+
1206 testAMM([&](AMM& ammAlice, Env& env) {
+
1207 // min tokens can't be <= zero
+
1208 ammAlice.deposit(
+
1209 carol, 0, XRP(100), tfSingleAsset, ter(temBAD_AMM_TOKENS));
1210 ammAlice.deposit(
-
1211 carol,
-
1212 1'000,
-
1213 XRP(0),
-
1214 USD(100),
-
1215 std::nullopt,
-
1216 tfTwoAsset,
+
1211 carol, -1, XRP(100), tfSingleAsset, ter(temBAD_AMM_TOKENS));
+
1212 ammAlice.deposit(
+
1213 carol,
+
1214 0,
+
1215 XRP(100),
+
1216 USD(100),
1217 std::nullopt,
-
1218 std::nullopt,
+
1218 tfTwoAsset,
1219 std::nullopt,
-
1220 ter(temBAD_AMOUNT));
-
1221 ammAlice.deposit(
-
1222 carol,
-
1223 1'000,
-
1224 XRP(100),
-
1225 USD(-1),
-
1226 std::nullopt,
-
1227 tfTwoAsset,
-
1228 std::nullopt,
+
1220 std::nullopt,
+
1221 std::nullopt,
+
1222 ter(temBAD_AMM_TOKENS));
+
1223 // min amounts can't be <= zero
+
1224 ammAlice.deposit(
+
1225 carol,
+
1226 1'000,
+
1227 XRP(0),
+
1228 USD(100),
1229 std::nullopt,
-
1230 std::nullopt,
-
1231 ter(temBAD_AMOUNT));
-
1232 // min amount bad currency
-
1233 ammAlice.deposit(
-
1234 carol,
-
1235 1'000,
-
1236 XRP(100),
-
1237 BAD(100),
-
1238 std::nullopt,
-
1239 tfTwoAsset,
+
1230 tfTwoAsset,
+
1231 std::nullopt,
+
1232 std::nullopt,
+
1233 std::nullopt,
+
1234 ter(temBAD_AMOUNT));
+
1235 ammAlice.deposit(
+
1236 carol,
+
1237 1'000,
+
1238 XRP(100),
+
1239 USD(-1),
1240 std::nullopt,
-
1241 std::nullopt,
+
1241 tfTwoAsset,
1242 std::nullopt,
-
1243 ter(temBAD_CURRENCY));
-
1244 // min amount bad token pair
-
1245 ammAlice.deposit(
-
1246 carol,
-
1247 1'000,
-
1248 XRP(100),
-
1249 XRP(100),
-
1250 std::nullopt,
-
1251 tfTwoAsset,
+
1243 std::nullopt,
+
1244 std::nullopt,
+
1245 ter(temBAD_AMOUNT));
+
1246 // min amount bad currency
+
1247 ammAlice.deposit(
+
1248 carol,
+
1249 1'000,
+
1250 XRP(100),
+
1251 BAD(100),
1252 std::nullopt,
-
1253 std::nullopt,
+
1253 tfTwoAsset,
1254 std::nullopt,
-
1255 ter(temBAD_AMM_TOKENS));
-
1256 ammAlice.deposit(
-
1257 carol,
-
1258 1'000,
-
1259 XRP(100),
-
1260 GBP(100),
-
1261 std::nullopt,
-
1262 tfTwoAsset,
-
1263 std::nullopt,
+
1255 std::nullopt,
+
1256 std::nullopt,
+
1257 ter(temBAD_CURRENCY));
+
1258 // min amount bad token pair
+
1259 ammAlice.deposit(
+
1260 carol,
+
1261 1'000,
+
1262 XRP(100),
+
1263 XRP(100),
1264 std::nullopt,
-
1265 std::nullopt,
-
1266 ter(temBAD_AMM_TOKENS));
-
1267 });
-
1268
-
1269 // Min deposit
-
1270 testAMM([&](AMM& ammAlice, Env& env) {
-
1271 // Equal deposit by tokens
-
1272 ammAlice.deposit(
-
1273 carol,
-
1274 1'000'000,
-
1275 XRP(1'000),
-
1276 USD(1'001),
+
1265 tfTwoAsset,
+
1266 std::nullopt,
+
1267 std::nullopt,
+
1268 std::nullopt,
+
1269 ter(temBAD_AMM_TOKENS));
+
1270 ammAlice.deposit(
+
1271 carol,
+
1272 1'000,
+
1273 XRP(100),
+
1274 GBP(100),
+
1275 std::nullopt,
+
1276 tfTwoAsset,
1277 std::nullopt,
-
1278 tfLPToken,
+
1278 std::nullopt,
1279 std::nullopt,
-
1280 std::nullopt,
-
1281 std::nullopt,
-
1282 ter(tecAMM_FAILED));
-
1283 ammAlice.deposit(
-
1284 carol,
-
1285 1'000'000,
-
1286 XRP(1'001),
-
1287 USD(1'000),
-
1288 std::nullopt,
-
1289 tfLPToken,
-
1290 std::nullopt,
+
1280 ter(temBAD_AMM_TOKENS));
+
1281 });
+
1282
+
1283 // Min deposit
+
1284 testAMM([&](AMM& ammAlice, Env& env) {
+
1285 // Equal deposit by tokens
+
1286 ammAlice.deposit(
+
1287 carol,
+
1288 1'000'000,
+
1289 XRP(1'000),
+
1290 USD(1'001),
1291 std::nullopt,
-
1292 std::nullopt,
-
1293 ter(tecAMM_FAILED));
-
1294 // Equal deposit by asset
-
1295 ammAlice.deposit(
-
1296 carol,
-
1297 100'001,
-
1298 XRP(100),
-
1299 USD(100),
-
1300 std::nullopt,
-
1301 tfTwoAsset,
+
1292 tfLPToken,
+
1293 std::nullopt,
+
1294 std::nullopt,
+
1295 std::nullopt,
+
1296 ter(tecAMM_FAILED));
+
1297 ammAlice.deposit(
+
1298 carol,
+
1299 1'000'000,
+
1300 XRP(1'001),
+
1301 USD(1'000),
1302 std::nullopt,
-
1303 std::nullopt,
+
1303 tfLPToken,
1304 std::nullopt,
-
1305 ter(tecAMM_FAILED));
-
1306 // Single deposit by asset
-
1307 ammAlice.deposit(
-
1308 carol,
-
1309 488'090,
-
1310 XRP(1'000),
-
1311 std::nullopt,
-
1312 std::nullopt,
-
1313 tfSingleAsset,
+
1305 std::nullopt,
+
1306 std::nullopt,
+
1307 ter(tecAMM_FAILED));
+
1308 // Equal deposit by asset
+
1309 ammAlice.deposit(
+
1310 carol,
+
1311 100'001,
+
1312 XRP(100),
+
1313 USD(100),
1314 std::nullopt,
-
1315 std::nullopt,
+
1315 tfTwoAsset,
1316 std::nullopt,
-
1317 ter(tecAMM_FAILED));
-
1318 });
-
1319 }
-
1320
-
1321 void
-
1322 testDeposit()
-
1323 {
-
1324 testcase("Deposit");
-
1325
-
1326 using namespace jtx;
-
1327
-
1328 // Equal deposit: 1000000 tokens, 10% of the current pool
-
1329 testAMM([&](AMM& ammAlice, Env& env) {
-
1330 auto const baseFee = env.current()->fees().base;
-
1331 ammAlice.deposit(carol, 1'000'000);
-
1332 BEAST_EXPECT(ammAlice.expectBalances(
-
1333 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
1334 // 30,000 less deposited 1,000
-
1335 BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
-
1336 // 30,000 less deposited 1,000 and 10 drops tx fee
-
1337 BEAST_EXPECT(expectLedgerEntryRoot(
-
1338 env, carol, XRPAmount{29'000'000'000 - baseFee}));
+
1317 std::nullopt,
+
1318 std::nullopt,
+
1319 ter(tecAMM_FAILED));
+
1320 // Single deposit by asset
+
1321 ammAlice.deposit(
+
1322 carol,
+
1323 488'090,
+
1324 XRP(1'000),
+
1325 std::nullopt,
+
1326 std::nullopt,
+
1327 tfSingleAsset,
+
1328 std::nullopt,
+
1329 std::nullopt,
+
1330 std::nullopt,
+
1331 ter(tecAMM_FAILED));
+
1332 });
+
1333
+
1334 // Equal deposit, tokens rounded to 0
+
1335 testAMM([&](AMM& amm, Env& env) {
+
1336 amm.deposit(DepositArg{
+
1337 .tokens = IOUAmount{1, -12},
+
1338 .err = ter(tecAMM_INVALID_TOKENS)});
1339 });
1340
-
1341 // equal asset deposit: unit test to exercise the rounding-down of
-
1342 // LPTokens in the AMMHelpers.cpp: adjustLPTokens calculations
-
1343 // The LPTokens need to have 16 significant digits and a fractional part
-
1344 for (Number const deltaLPTokens :
-
1345 {Number{UINT64_C(100000'0000000009), -10},
-
1346 Number{UINT64_C(100000'0000000001), -10}})
-
1347 {
-
1348 testAMM([&](AMM& ammAlice, Env& env) {
-
1349 // initial LPToken balance
-
1350 IOUAmount const initLPToken = ammAlice.getLPTokensBalance();
-
1351 IOUAmount const newLPTokens{
-
1352 deltaLPTokens.mantissa(), deltaLPTokens.exponent()};
-
1353
-
1354 // carol performs a two-asset deposit
-
1355 ammAlice.deposit(
-
1356 DepositArg{.account = carol, .tokens = newLPTokens});
+
1341 // Equal deposit limit, tokens rounded to 0
+
1342 testAMM(
+
1343 [&](AMM& amm, Env& env) {
+
1344 amm.deposit(DepositArg{
+
1345 .asset1In = STAmount{USD, 1, -15},
+
1346 .asset2In = XRPAmount{1},
+
1347 .err = ter(tecAMM_INVALID_TOKENS)});
+
1348 },
+
1349 {.pool = {{USD(1'000'000), XRP(1'000'000)}},
+
1350 .features = {features - fixAMMv1_3}});
+
1351 testAMM([&](AMM& amm, Env& env) {
+
1352 amm.deposit(DepositArg{
+
1353 .asset1In = STAmount{USD, 1, -15},
+
1354 .asset2In = XRPAmount{1},
+
1355 .err = ter(tecAMM_INVALID_TOKENS)});
+
1356 });
1357
-
1358 IOUAmount const finalLPToken = ammAlice.getLPTokensBalance();
-
1359
-
1360 // Change in behavior due to rounding down of LPTokens:
-
1361 // there is a decrease in the observed return of LPTokens --
-
1362 // Inputs Number{UINT64_C(100000'0000000001), -10} and
-
1363 // Number{UINT64_C(100000'0000000009), -10} are both rounded
-
1364 // down to 1e5
-
1365 BEAST_EXPECT((finalLPToken - initLPToken == IOUAmount{1, 5}));
-
1366 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
-
1367
-
1368 // fraction of newLPTokens/(existing LPToken balance). The
-
1369 // existing LPToken balance is 1e7
-
1370 Number const fr = deltaLPTokens / 1e7;
-
1371
-
1372 // The below equations are based on Equation 1, 2 from XLS-30d
-
1373 // specification, Section: 2.3.1.2
-
1374 Number const deltaXRP = fr * 1e10;
-
1375 Number const deltaUSD = fr * 1e4;
-
1376
-
1377 STAmount const depositUSD =
-
1378 STAmount{USD, deltaUSD.mantissa(), deltaUSD.exponent()};
-
1379
-
1380 STAmount const depositXRP =
-
1381 STAmount{XRP, deltaXRP.mantissa(), deltaXRP.exponent()};
-
1382
-
1383 // initial LPTokens (1e7) + newLPTokens
-
1384 BEAST_EXPECT(ammAlice.expectBalances(
-
1385 XRP(10'000) + depositXRP,
-
1386 USD(10'000) + depositUSD,
-
1387 IOUAmount{1, 7} + newLPTokens));
-
1388
-
1389 // 30,000 less deposited depositUSD
-
1390 BEAST_EXPECT(expectLine(env, carol, USD(30'000) - depositUSD));
-
1391 // 30,000 less deposited depositXRP and 10 drops tx fee
-
1392 BEAST_EXPECT(expectLedgerEntryRoot(
-
1393 env, carol, XRP(30'000) - depositXRP - txfee(env, 1)));
-
1394 });
-
1395 }
-
1396
-
1397 // Equal limit deposit: deposit USD100 and XRP proportionally
-
1398 // to the pool composition not to exceed 100XRP. If the amount
-
1399 // exceeds 100XRP then deposit 100XRP and USD proportionally
-
1400 // to the pool composition not to exceed 100USD. Fail if exceeded.
-
1401 // Deposit 100USD/100XRP
-
1402 testAMM([&](AMM& ammAlice, Env&) {
-
1403 ammAlice.deposit(carol, USD(100), XRP(100));
-
1404 BEAST_EXPECT(ammAlice.expectBalances(
-
1405 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
-
1406 });
-
1407
-
1408 // Equal limit deposit.
-
1409 // Try to deposit 200USD/100XRP. Is truncated to 100USD/100XRP.
-
1410 testAMM([&](AMM& ammAlice, Env&) {
-
1411 ammAlice.deposit(carol, USD(200), XRP(100));
-
1412 BEAST_EXPECT(ammAlice.expectBalances(
-
1413 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
-
1414 });
-
1415 // Try to deposit 100USD/200XRP. Is truncated to 100USD/100XRP.
-
1416 testAMM([&](AMM& ammAlice, Env&) {
-
1417 ammAlice.deposit(carol, USD(100), XRP(200));
-
1418 BEAST_EXPECT(ammAlice.expectBalances(
-
1419 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
-
1420 });
+
1358 // Single deposit by asset, tokens rounded to 0
+
1359 testAMM([&](AMM& amm, Env& env) {
+
1360 amm.deposit(DepositArg{
+
1361 .asset1In = STAmount{USD, 1, -15},
+
1362 .err = ter(tecAMM_INVALID_TOKENS)});
+
1363 });
+
1364
+
1365 // Single deposit by tokens, tokens rounded to 0
+
1366 testAMM([&](AMM& amm, Env& env) {
+
1367 amm.deposit(DepositArg{
+
1368 .tokens = IOUAmount{1, -10},
+
1369 .asset1In = STAmount{USD, 1, -15},
+
1370 .err = ter(tecAMM_INVALID_TOKENS)});
+
1371 });
+
1372
+
1373 // Single deposit with eprice, tokens rounded to 0
+
1374 testAMM([&](AMM& amm, Env& env) {
+
1375 amm.deposit(DepositArg{
+
1376 .asset1In = STAmount{USD, 1, -15},
+
1377 .maxEP = STAmount{USD, 1, -1},
+
1378 .err = ter(tecAMM_INVALID_TOKENS)});
+
1379 });
+
1380 }
+
1381
+
1382 void
+
1383 testDeposit()
+
1384 {
+
1385 testcase("Deposit");
+
1386
+
1387 using namespace jtx;
+
1388 auto const all = supported_amendments();
+
1389
+
1390 // Equal deposit: 1000000 tokens, 10% of the current pool
+
1391 testAMM([&](AMM& ammAlice, Env& env) {
+
1392 auto const baseFee = env.current()->fees().base;
+
1393 ammAlice.deposit(carol, 1'000'000);
+
1394 BEAST_EXPECT(ammAlice.expectBalances(
+
1395 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
1396 // 30,000 less deposited 1,000
+
1397 BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
+
1398 // 30,000 less deposited 1,000 and 10 drops tx fee
+
1399 BEAST_EXPECT(expectLedgerEntryRoot(
+
1400 env, carol, XRPAmount{29'000'000'000 - baseFee}));
+
1401 });
+
1402
+
1403 // equal asset deposit: unit test to exercise the rounding-down of
+
1404 // LPTokens in the AMMHelpers.cpp: adjustLPTokens calculations
+
1405 // The LPTokens need to have 16 significant digits and a fractional part
+
1406 for (Number const deltaLPTokens :
+
1407 {Number{UINT64_C(100000'0000000009), -10},
+
1408 Number{UINT64_C(100000'0000000001), -10}})
+
1409 {
+
1410 testAMM([&](AMM& ammAlice, Env& env) {
+
1411 // initial LPToken balance
+
1412 IOUAmount const initLPToken = ammAlice.getLPTokensBalance();
+
1413 IOUAmount const newLPTokens{
+
1414 deltaLPTokens.mantissa(), deltaLPTokens.exponent()};
+
1415
+
1416 // carol performs a two-asset deposit
+
1417 ammAlice.deposit(
+
1418 DepositArg{.account = carol, .tokens = newLPTokens});
+
1419
+
1420 IOUAmount const finalLPToken = ammAlice.getLPTokensBalance();
1421
-
1422 // Single deposit: 1000 USD
-
1423 testAMM([&](AMM& ammAlice, Env&) {
-
1424 ammAlice.deposit(carol, USD(1'000));
-
1425 BEAST_EXPECT(ammAlice.expectBalances(
-
1426 XRP(10'000),
-
1427 STAmount{USD, UINT64_C(10'999'99999999999), -11},
-
1428 IOUAmount{10'488'088'48170151, -8}));
-
1429 });
-
1430
-
1431 // Single deposit: 1000 XRP
-
1432 testAMM([&](AMM& ammAlice, Env&) {
-
1433 ammAlice.deposit(carol, XRP(1'000));
-
1434 BEAST_EXPECT(ammAlice.expectBalances(
-
1435 XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
-
1436 });
-
1437
-
1438 // Single deposit: 100000 tokens worth of USD
-
1439 testAMM([&](AMM& ammAlice, Env&) {
-
1440 ammAlice.deposit(carol, 100000, USD(205));
-
1441 BEAST_EXPECT(ammAlice.expectBalances(
-
1442 XRP(10'000), USD(10'201), IOUAmount{10'100'000, 0}));
-
1443 });
+
1422 // Change in behavior due to rounding down of LPTokens:
+
1423 // there is a decrease in the observed return of LPTokens --
+
1424 // Inputs Number{UINT64_C(100000'0000000001), -10} and
+
1425 // Number{UINT64_C(100000'0000000009), -10} are both rounded
+
1426 // down to 1e5
+
1427 BEAST_EXPECT((finalLPToken - initLPToken == IOUAmount{1, 5}));
+
1428 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
+
1429
+
1430 // fraction of newLPTokens/(existing LPToken balance). The
+
1431 // existing LPToken balance is 1e7
+
1432 Number const fr = deltaLPTokens / 1e7;
+
1433
+
1434 // The below equations are based on Equation 1, 2 from XLS-30d
+
1435 // specification, Section: 2.3.1.2
+
1436 Number const deltaXRP = fr * 1e10;
+
1437 Number const deltaUSD = fr * 1e4;
+
1438
+
1439 STAmount const depositUSD =
+
1440 STAmount{USD, deltaUSD.mantissa(), deltaUSD.exponent()};
+
1441
+
1442 STAmount const depositXRP =
+
1443 STAmount{XRP, deltaXRP.mantissa(), deltaXRP.exponent()};
1444
-
1445 // Single deposit: 100000 tokens worth of XRP
-
1446 testAMM([&](AMM& ammAlice, Env&) {
-
1447 ammAlice.deposit(carol, 100'000, XRP(205));
-
1448 BEAST_EXPECT(ammAlice.expectBalances(
-
1449 XRP(10'201), USD(10'000), IOUAmount{10'100'000, 0}));
-
1450 });
-
1451
-
1452 // Single deposit with EP not exceeding specified:
-
1453 // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut)
-
1454 testAMM([&](AMM& ammAlice, Env&) {
-
1455 ammAlice.deposit(
-
1456 carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
-
1457 BEAST_EXPECT(ammAlice.expectBalances(
-
1458 XRP(10'000),
-
1459 STAmount{USD, UINT64_C(10'999'99999999999), -11},
-
1460 IOUAmount{10'488'088'48170151, -8}));
-
1461 });
-
1462
-
1463 // Single deposit with EP not exceeding specified:
-
1464 // 100USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
-
1465 testAMM([&](AMM& ammAlice, Env&) {
-
1466 ammAlice.deposit(
-
1467 carol, USD(100), std::nullopt, STAmount{USD, 2004, -6});
-
1468 BEAST_EXPECT(ammAlice.expectBalances(
-
1469 XRP(10'000),
-
1470 STAmount{USD, 10'080'16, -2},
-
1471 IOUAmount{10'040'000, 0}));
-
1472 });
-
1473
-
1474 // Single deposit with EP not exceeding specified:
-
1475 // 0USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
-
1476 testAMM([&](AMM& ammAlice, Env&) {
-
1477 ammAlice.deposit(
-
1478 carol, USD(0), std::nullopt, STAmount{USD, 2004, -6});
-
1479 BEAST_EXPECT(ammAlice.expectBalances(
-
1480 XRP(10'000),
-
1481 STAmount{USD, 10'080'16, -2},
-
1482 IOUAmount{10'040'000, 0}));
-
1483 });
-
1484
-
1485 // IOU to IOU + transfer fee
-
1486 {
-
1487 Env env{*this};
-
1488 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
-
1489 env(rate(gw, 1.25));
-
1490 env.close();
-
1491 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
-
1492 BEAST_EXPECT(ammAlice.expectBalances(
-
1493 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
-
1494 BEAST_EXPECT(expectLine(env, alice, USD(0)));
-
1495 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
-
1496 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
-
1497 // no transfer fee on deposit
-
1498 ammAlice.deposit(carol, 10);
-
1499 BEAST_EXPECT(ammAlice.expectBalances(
-
1500 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
-
1501 BEAST_EXPECT(expectLine(env, carol, USD(0)));
-
1502 BEAST_EXPECT(expectLine(env, carol, BTC(0)));
-
1503 }
-
1504
-
1505 // Tiny deposits
-
1506 testAMM([&](AMM& ammAlice, Env&) {
-
1507 ammAlice.deposit(carol, IOUAmount{1, -3});
-
1508 BEAST_EXPECT(ammAlice.expectBalances(
-
1509 XRPAmount{10'000'000'001},
-
1510 STAmount{USD, UINT64_C(10'000'000001), -6},
-
1511 IOUAmount{10'000'000'001, -3}));
-
1512 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1, -3}));
-
1513 });
-
1514 testAMM([&](AMM& ammAlice, Env&) {
-
1515 ammAlice.deposit(carol, XRPAmount{1});
-
1516 BEAST_EXPECT(ammAlice.expectBalances(
-
1517 XRPAmount{10'000'000'001},
-
1518 USD(10'000),
-
1519 IOUAmount{1'000'000'000049999, -8}));
-
1520 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{49999, -8}));
-
1521 });
-
1522 testAMM([&](AMM& ammAlice, Env&) {
-
1523 ammAlice.deposit(carol, STAmount{USD, 1, -10});
-
1524 BEAST_EXPECT(ammAlice.expectBalances(
-
1525 XRP(10'000),
-
1526 STAmount{USD, UINT64_C(10'000'00000000008), -11},
-
1527 IOUAmount{10'000'000'00000004, -8}));
-
1528 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4, -8}));
-
1529 });
-
1530
-
1531 // Issuer create/deposit
-
1532 {
-
1533 Env env(*this);
-
1534 env.fund(XRP(30000), gw);
-
1535 AMM ammGw(env, gw, XRP(10'000), USD(10'000));
-
1536 BEAST_EXPECT(
-
1537 ammGw.expectBalances(XRP(10'000), USD(10'000), ammGw.tokens()));
-
1538 ammGw.deposit(gw, 1'000'000);
-
1539 BEAST_EXPECT(ammGw.expectBalances(
-
1540 XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
-
1541 ammGw.deposit(gw, USD(1'000));
-
1542 BEAST_EXPECT(ammGw.expectBalances(
-
1543 XRP(11'000),
-
1544 STAmount{USD, UINT64_C(11'999'99999999998), -11},
-
1545 IOUAmount{11'489'125'29307605, -8}));
-
1546 }
-
1547
-
1548 // Issuer deposit
-
1549 testAMM([&](AMM& ammAlice, Env& env) {
-
1550 ammAlice.deposit(gw, 1'000'000);
-
1551 BEAST_EXPECT(ammAlice.expectBalances(
-
1552 XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
-
1553 ammAlice.deposit(gw, USD(1'000));
+
1445 // initial LPTokens (1e7) + newLPTokens
+
1446 BEAST_EXPECT(ammAlice.expectBalances(
+
1447 XRP(10'000) + depositXRP,
+
1448 USD(10'000) + depositUSD,
+
1449 IOUAmount{1, 7} + newLPTokens));
+
1450
+
1451 // 30,000 less deposited depositUSD
+
1452 BEAST_EXPECT(expectLine(env, carol, USD(30'000) - depositUSD));
+
1453 // 30,000 less deposited depositXRP and 10 drops tx fee
+
1454 BEAST_EXPECT(expectLedgerEntryRoot(
+
1455 env, carol, XRP(30'000) - depositXRP - txfee(env, 1)));
+
1456 });
+
1457 }
+
1458
+
1459 // Equal limit deposit: deposit USD100 and XRP proportionally
+
1460 // to the pool composition not to exceed 100XRP. If the amount
+
1461 // exceeds 100XRP then deposit 100XRP and USD proportionally
+
1462 // to the pool composition not to exceed 100USD. Fail if exceeded.
+
1463 // Deposit 100USD/100XRP
+
1464 testAMM([&](AMM& ammAlice, Env&) {
+
1465 ammAlice.deposit(carol, USD(100), XRP(100));
+
1466 BEAST_EXPECT(ammAlice.expectBalances(
+
1467 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
+
1468 });
+
1469
+
1470 // Equal limit deposit.
+
1471 // Try to deposit 200USD/100XRP. Is truncated to 100USD/100XRP.
+
1472 testAMM([&](AMM& ammAlice, Env&) {
+
1473 ammAlice.deposit(carol, USD(200), XRP(100));
+
1474 BEAST_EXPECT(ammAlice.expectBalances(
+
1475 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
+
1476 });
+
1477 // Try to deposit 100USD/200XRP. Is truncated to 100USD/100XRP.
+
1478 testAMM([&](AMM& ammAlice, Env&) {
+
1479 ammAlice.deposit(carol, USD(100), XRP(200));
+
1480 BEAST_EXPECT(ammAlice.expectBalances(
+
1481 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
+
1482 });
+
1483
+
1484 // Single deposit: 1000 USD
+
1485 testAMM([&](AMM& ammAlice, Env&) {
+
1486 ammAlice.deposit(carol, USD(1'000));
+
1487 BEAST_EXPECT(ammAlice.expectBalances(
+
1488 XRP(10'000),
+
1489 STAmount{USD, UINT64_C(10'999'99999999999), -11},
+
1490 IOUAmount{10'488'088'48170151, -8}));
+
1491 });
+
1492
+
1493 // Single deposit: 1000 XRP
+
1494 testAMM([&](AMM& ammAlice, Env&) {
+
1495 ammAlice.deposit(carol, XRP(1'000));
+
1496 BEAST_EXPECT(ammAlice.expectBalances(
+
1497 XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
+
1498 });
+
1499
+
1500 // Single deposit: 100000 tokens worth of USD
+
1501 testAMM([&](AMM& ammAlice, Env&) {
+
1502 ammAlice.deposit(carol, 100000, USD(205));
+
1503 BEAST_EXPECT(ammAlice.expectBalances(
+
1504 XRP(10'000), USD(10'201), IOUAmount{10'100'000, 0}));
+
1505 });
+
1506
+
1507 // Single deposit: 100000 tokens worth of XRP
+
1508 testAMM([&](AMM& ammAlice, Env&) {
+
1509 ammAlice.deposit(carol, 100'000, XRP(205));
+
1510 BEAST_EXPECT(ammAlice.expectBalances(
+
1511 XRP(10'201), USD(10'000), IOUAmount{10'100'000, 0}));
+
1512 });
+
1513
+
1514 // Single deposit with EP not exceeding specified:
+
1515 // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut)
+
1516 testAMM([&](AMM& ammAlice, Env&) {
+
1517 ammAlice.deposit(
+
1518 carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
+
1519 BEAST_EXPECT(ammAlice.expectBalances(
+
1520 XRP(10'000),
+
1521 STAmount{USD, UINT64_C(10'999'99999999999), -11},
+
1522 IOUAmount{10'488'088'48170151, -8}));
+
1523 });
+
1524
+
1525 // Single deposit with EP not exceeding specified:
+
1526 // 100USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
+
1527 testAMM([&](AMM& ammAlice, Env&) {
+
1528 ammAlice.deposit(
+
1529 carol, USD(100), std::nullopt, STAmount{USD, 2004, -6});
+
1530 BEAST_EXPECT(ammAlice.expectBalances(
+
1531 XRP(10'000),
+
1532 STAmount{USD, 10'080'16, -2},
+
1533 IOUAmount{10'040'000, 0}));
+
1534 });
+
1535
+
1536 // Single deposit with EP not exceeding specified:
+
1537 // 0USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
+
1538 testAMM([&](AMM& ammAlice, Env&) {
+
1539 ammAlice.deposit(
+
1540 carol, USD(0), std::nullopt, STAmount{USD, 2004, -6});
+
1541 BEAST_EXPECT(ammAlice.expectBalances(
+
1542 XRP(10'000),
+
1543 STAmount{USD, 10'080'16, -2},
+
1544 IOUAmount{10'040'000, 0}));
+
1545 });
+
1546
+
1547 // IOU to IOU + transfer fee
+
1548 {
+
1549 Env env{*this};
+
1550 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
+
1551 env(rate(gw, 1.25));
+
1552 env.close();
+
1553 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
1554 BEAST_EXPECT(ammAlice.expectBalances(
-
1555 XRP(11'000),
-
1556 STAmount{USD, UINT64_C(11'999'99999999998), -11},
-
1557 IOUAmount{11'489'125'29307605, -8}));
-
1558 });
-
1559
-
1560 // Min deposit
-
1561 testAMM([&](AMM& ammAlice, Env& env) {
-
1562 // Equal deposit by tokens
-
1563 ammAlice.deposit(
-
1564 carol,
-
1565 1'000'000,
-
1566 XRP(1'000),
-
1567 USD(1'000),
-
1568 std::nullopt,
-
1569 tfLPToken,
-
1570 std::nullopt,
-
1571 std::nullopt);
-
1572 BEAST_EXPECT(ammAlice.expectBalances(
-
1573 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
1574 });
-
1575 testAMM([&](AMM& ammAlice, Env& env) {
-
1576 // Equal deposit by asset
-
1577 ammAlice.deposit(
-
1578 carol,
-
1579 1'000'000,
-
1580 XRP(1'000),
-
1581 USD(1'000),
-
1582 std::nullopt,
-
1583 tfTwoAsset,
-
1584 std::nullopt,
-
1585 std::nullopt);
+
1555 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
+
1556 BEAST_EXPECT(expectLine(env, alice, USD(0)));
+
1557 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
+
1558 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
+
1559 // no transfer fee on deposit
+
1560 ammAlice.deposit(carol, 10);
+
1561 BEAST_EXPECT(ammAlice.expectBalances(
+
1562 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
+
1563 BEAST_EXPECT(expectLine(env, carol, USD(0)));
+
1564 BEAST_EXPECT(expectLine(env, carol, BTC(0)));
+
1565 }
+
1566
+
1567 // Tiny deposits
+
1568 testAMM([&](AMM& ammAlice, Env&) {
+
1569 ammAlice.deposit(carol, IOUAmount{1, -3});
+
1570 BEAST_EXPECT(ammAlice.expectBalances(
+
1571 XRPAmount{10'000'000'001},
+
1572 STAmount{USD, UINT64_C(10'000'000001), -6},
+
1573 IOUAmount{10'000'000'001, -3}));
+
1574 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1, -3}));
+
1575 });
+
1576 testAMM([&](AMM& ammAlice, Env&) {
+
1577 ammAlice.deposit(carol, XRPAmount{1});
+
1578 BEAST_EXPECT(ammAlice.expectBalances(
+
1579 XRPAmount{10'000'000'001},
+
1580 USD(10'000),
+
1581 IOUAmount{1'000'000'000049999, -8}));
+
1582 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{49999, -8}));
+
1583 });
+
1584 testAMM([&](AMM& ammAlice, Env&) {
+
1585 ammAlice.deposit(carol, STAmount{USD, 1, -10});
1586 BEAST_EXPECT(ammAlice.expectBalances(
-
1587 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
1588 });
-
1589 testAMM([&](AMM& ammAlice, Env& env) {
-
1590 // Single deposit by asset
-
1591 ammAlice.deposit(
-
1592 carol,
-
1593 488'088,
-
1594 XRP(1'000),
-
1595 std::nullopt,
-
1596 std::nullopt,
-
1597 tfSingleAsset,
-
1598 std::nullopt,
-
1599 std::nullopt);
-
1600 BEAST_EXPECT(ammAlice.expectBalances(
-
1601 XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
-
1602 });
-
1603 testAMM([&](AMM& ammAlice, Env& env) {
-
1604 // Single deposit by asset
-
1605 ammAlice.deposit(
-
1606 carol,
-
1607 488'088,
-
1608 USD(1'000),
-
1609 std::nullopt,
-
1610 std::nullopt,
-
1611 tfSingleAsset,
-
1612 std::nullopt,
-
1613 std::nullopt);
+
1587 XRP(10'000),
+
1588 STAmount{USD, UINT64_C(10'000'00000000008), -11},
+
1589 IOUAmount{10'000'000'00000004, -8}));
+
1590 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4, -8}));
+
1591 });
+
1592
+
1593 // Issuer create/deposit
+
1594 for (auto const& feat : {all, all - fixAMMv1_3})
+
1595 {
+
1596 Env env(*this, feat);
+
1597 env.fund(XRP(30000), gw);
+
1598 AMM ammGw(env, gw, XRP(10'000), USD(10'000));
+
1599 BEAST_EXPECT(
+
1600 ammGw.expectBalances(XRP(10'000), USD(10'000), ammGw.tokens()));
+
1601 ammGw.deposit(gw, 1'000'000);
+
1602 BEAST_EXPECT(ammGw.expectBalances(
+
1603 XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
+
1604 ammGw.deposit(gw, USD(1'000));
+
1605 BEAST_EXPECT(ammGw.expectBalances(
+
1606 XRP(11'000),
+
1607 STAmount{USD, UINT64_C(11'999'99999999998), -11},
+
1608 IOUAmount{11'489'125'29307605, -8}));
+
1609 }
+
1610
+
1611 // Issuer deposit
+
1612 testAMM([&](AMM& ammAlice, Env& env) {
+
1613 ammAlice.deposit(gw, 1'000'000);
1614 BEAST_EXPECT(ammAlice.expectBalances(
-
1615 XRP(10'000),
-
1616 STAmount{USD, UINT64_C(10'999'99999999999), -11},
-
1617 IOUAmount{10'488'088'48170151, -8}));
-
1618 });
-
1619 }
-
1620
-
1621 void
-
1622 testInvalidWithdraw()
-
1623 {
-
1624 testcase("Invalid Withdraw");
-
1625
-
1626 using namespace jtx;
-
1627
-
1628 testAMM(
-
1629 [&](AMM& ammAlice, Env& env) {
-
1630 WithdrawArg args{
-
1631 .asset1Out = XRP(100),
-
1632 .err = ter(tecAMM_BALANCE),
-
1633 };
-
1634 ammAlice.withdraw(args);
-
1635 },
-
1636 {{XRP(99), USD(99)}});
-
1637
-
1638 testAMM(
-
1639 [&](AMM& ammAlice, Env& env) {
-
1640 WithdrawArg args{
-
1641 .asset1Out = USD(100),
-
1642 .err = ter(tecAMM_BALANCE),
-
1643 };
-
1644 ammAlice.withdraw(args);
-
1645 },
-
1646 {{XRP(99), USD(99)}});
-
1647
-
1648 {
-
1649 Env env{*this};
-
1650 env.fund(XRP(30'000), gw, alice, bob);
-
1651 env.close();
-
1652 env(fset(gw, asfRequireAuth));
-
1653 env.close();
-
1654 env(trust(alice, gw["USD"](30'000), 0));
-
1655 env(trust(gw, alice["USD"](0), tfSetfAuth));
-
1656 // Bob trusts Gateway to owe him USD...
-
1657 env(trust(bob, gw["USD"](30'000), 0));
-
1658 // ...but Gateway does not authorize Bob to hold its USD.
-
1659 env.close();
-
1660 env(pay(gw, alice, USD(10'000)));
-
1661 env.close();
-
1662 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
-
1663 WithdrawArg args{
-
1664 .account = bob,
-
1665 .asset1Out = USD(100),
-
1666 .err = ter(tecNO_AUTH),
-
1667 };
-
1668 ammAlice.withdraw(args);
-
1669 }
-
1670
-
1671 testAMM([&](AMM& ammAlice, Env& env) {
-
1672 // Invalid flags
-
1673 ammAlice.withdraw(
-
1674 alice,
-
1675 1'000'000,
-
1676 std::nullopt,
-
1677 std::nullopt,
-
1678 std::nullopt,
-
1679 tfBurnable,
-
1680 std::nullopt,
-
1681 std::nullopt,
-
1682 ter(temINVALID_FLAG));
-
1683 ammAlice.withdraw(
-
1684 alice,
-
1685 1'000'000,
-
1686 std::nullopt,
-
1687 std::nullopt,
-
1688 std::nullopt,
-
1689 tfTwoAssetIfEmpty,
-
1690 std::nullopt,
-
1691 std::nullopt,
-
1692 ter(temINVALID_FLAG));
-
1693
-
1694 // Invalid options
-
1695 std::vector<std::tuple<
-
1696 std::optional<std::uint32_t>,
-
1697 std::optional<STAmount>,
-
1698 std::optional<STAmount>,
-
1699 std::optional<IOUAmount>,
-
1700 std::optional<std::uint32_t>,
-
1701 NotTEC>>
-
1702 invalidOptions = {
-
1703 // tokens, asset1Out, asset2Out, EPrice, flags, ter
-
1704 {std::nullopt,
-
1705 std::nullopt,
-
1706 std::nullopt,
-
1707 std::nullopt,
-
1708 std::nullopt,
-
1709 temMALFORMED},
-
1710 {std::nullopt,
-
1711 std::nullopt,
-
1712 std::nullopt,
-
1713 std::nullopt,
-
1714 tfSingleAsset | tfTwoAsset,
-
1715 temMALFORMED},
-
1716 {1'000,
-
1717 std::nullopt,
-
1718 std::nullopt,
-
1719 std::nullopt,
-
1720 tfWithdrawAll,
-
1721 temMALFORMED},
-
1722 {std::nullopt,
-
1723 USD(0),
-
1724 XRP(100),
-
1725 std::nullopt,
-
1726 tfWithdrawAll | tfLPToken,
-
1727 temMALFORMED},
-
1728 {std::nullopt,
-
1729 std::nullopt,
-
1730 USD(100),
-
1731 std::nullopt,
-
1732 tfWithdrawAll,
-
1733 temMALFORMED},
-
1734 {std::nullopt,
-
1735 std::nullopt,
-
1736 std::nullopt,
-
1737 std::nullopt,
-
1738 tfWithdrawAll | tfOneAssetWithdrawAll,
-
1739 temMALFORMED},
-
1740 {std::nullopt,
-
1741 USD(100),
-
1742 std::nullopt,
-
1743 std::nullopt,
-
1744 tfWithdrawAll,
-
1745 temMALFORMED},
-
1746 {std::nullopt,
-
1747 std::nullopt,
-
1748 std::nullopt,
-
1749 std::nullopt,
-
1750 tfOneAssetWithdrawAll,
-
1751 temMALFORMED},
-
1752 {1'000,
-
1753 std::nullopt,
-
1754 USD(100),
-
1755 std::nullopt,
-
1756 std::nullopt,
-
1757 temMALFORMED},
-
1758 {std::nullopt,
-
1759 std::nullopt,
-
1760 std::nullopt,
-
1761 IOUAmount{250, 0},
-
1762 tfWithdrawAll,
-
1763 temMALFORMED},
-
1764 {1'000,
-
1765 std::nullopt,
-
1766 std::nullopt,
-
1767 IOUAmount{250, 0},
-
1768 std::nullopt,
-
1769 temMALFORMED},
-
1770 {std::nullopt,
+
1615 XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
+
1616 ammAlice.deposit(gw, USD(1'000));
+
1617 BEAST_EXPECT(ammAlice.expectBalances(
+
1618 XRP(11'000),
+
1619 STAmount{USD, UINT64_C(11'999'99999999998), -11},
+
1620 IOUAmount{11'489'125'29307605, -8}));
+
1621 });
+
1622
+
1623 // Min deposit
+
1624 testAMM([&](AMM& ammAlice, Env& env) {
+
1625 // Equal deposit by tokens
+
1626 ammAlice.deposit(
+
1627 carol,
+
1628 1'000'000,
+
1629 XRP(1'000),
+
1630 USD(1'000),
+
1631 std::nullopt,
+
1632 tfLPToken,
+
1633 std::nullopt,
+
1634 std::nullopt);
+
1635 BEAST_EXPECT(ammAlice.expectBalances(
+
1636 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
1637 });
+
1638 testAMM([&](AMM& ammAlice, Env& env) {
+
1639 // Equal deposit by asset
+
1640 ammAlice.deposit(
+
1641 carol,
+
1642 1'000'000,
+
1643 XRP(1'000),
+
1644 USD(1'000),
+
1645 std::nullopt,
+
1646 tfTwoAsset,
+
1647 std::nullopt,
+
1648 std::nullopt);
+
1649 BEAST_EXPECT(ammAlice.expectBalances(
+
1650 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
1651 });
+
1652 testAMM([&](AMM& ammAlice, Env& env) {
+
1653 // Single deposit by asset
+
1654 ammAlice.deposit(
+
1655 carol,
+
1656 488'088,
+
1657 XRP(1'000),
+
1658 std::nullopt,
+
1659 std::nullopt,
+
1660 tfSingleAsset,
+
1661 std::nullopt,
+
1662 std::nullopt);
+
1663 BEAST_EXPECT(ammAlice.expectBalances(
+
1664 XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
+
1665 });
+
1666 testAMM([&](AMM& ammAlice, Env& env) {
+
1667 // Single deposit by asset
+
1668 ammAlice.deposit(
+
1669 carol,
+
1670 488'088,
+
1671 USD(1'000),
+
1672 std::nullopt,
+
1673 std::nullopt,
+
1674 tfSingleAsset,
+
1675 std::nullopt,
+
1676 std::nullopt);
+
1677 BEAST_EXPECT(ammAlice.expectBalances(
+
1678 XRP(10'000),
+
1679 STAmount{USD, UINT64_C(10'999'99999999999), -11},
+
1680 IOUAmount{10'488'088'48170151, -8}));
+
1681 });
+
1682 }
+
1683
+
1684 void
+
1685 testInvalidWithdraw()
+
1686 {
+
1687 testcase("Invalid Withdraw");
+
1688
+
1689 using namespace jtx;
+
1690 auto const all = supported_amendments();
+
1691
+
1692 testAMM(
+
1693 [&](AMM& ammAlice, Env& env) {
+
1694 WithdrawArg args{
+
1695 .asset1Out = XRP(100),
+
1696 .err = ter(tecAMM_BALANCE),
+
1697 };
+
1698 ammAlice.withdraw(args);
+
1699 },
+
1700 {{XRP(99), USD(99)}});
+
1701
+
1702 testAMM(
+
1703 [&](AMM& ammAlice, Env& env) {
+
1704 WithdrawArg args{
+
1705 .asset1Out = USD(100),
+
1706 .err = ter(tecAMM_BALANCE),
+
1707 };
+
1708 ammAlice.withdraw(args);
+
1709 },
+
1710 {{XRP(99), USD(99)}});
+
1711
+
1712 {
+
1713 Env env{*this};
+
1714 env.fund(XRP(30'000), gw, alice, bob);
+
1715 env.close();
+
1716 env(fset(gw, asfRequireAuth));
+
1717 env.close();
+
1718 env(trust(alice, gw["USD"](30'000), 0));
+
1719 env(trust(gw, alice["USD"](0), tfSetfAuth));
+
1720 // Bob trusts Gateway to owe him USD...
+
1721 env(trust(bob, gw["USD"](30'000), 0));
+
1722 // ...but Gateway does not authorize Bob to hold its USD.
+
1723 env.close();
+
1724 env(pay(gw, alice, USD(10'000)));
+
1725 env.close();
+
1726 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
+
1727 WithdrawArg args{
+
1728 .account = bob,
+
1729 .asset1Out = USD(100),
+
1730 .err = ter(tecNO_AUTH),
+
1731 };
+
1732 ammAlice.withdraw(args);
+
1733 }
+
1734
+
1735 testAMM([&](AMM& ammAlice, Env& env) {
+
1736 // Invalid flags
+
1737 ammAlice.withdraw(
+
1738 alice,
+
1739 1'000'000,
+
1740 std::nullopt,
+
1741 std::nullopt,
+
1742 std::nullopt,
+
1743 tfBurnable,
+
1744 std::nullopt,
+
1745 std::nullopt,
+
1746 ter(temINVALID_FLAG));
+
1747 ammAlice.withdraw(
+
1748 alice,
+
1749 1'000'000,
+
1750 std::nullopt,
+
1751 std::nullopt,
+
1752 std::nullopt,
+
1753 tfTwoAssetIfEmpty,
+
1754 std::nullopt,
+
1755 std::nullopt,
+
1756 ter(temINVALID_FLAG));
+
1757
+
1758 // Invalid options
+
1759 std::vector<std::tuple<
+
1760 std::optional<std::uint32_t>,
+
1761 std::optional<STAmount>,
+
1762 std::optional<STAmount>,
+
1763 std::optional<IOUAmount>,
+
1764 std::optional<std::uint32_t>,
+
1765 NotTEC>>
+
1766 invalidOptions = {
+
1767 // tokens, asset1Out, asset2Out, EPrice, flags, ter
+
1768 {std::nullopt,
+
1769 std::nullopt,
+
1770 std::nullopt,
1771 std::nullopt,
-
1772 USD(100),
-
1773 IOUAmount{250, 0},
-
1774 std::nullopt,
-
1775 temMALFORMED},
-
1776 {std::nullopt,
-
1777 XRP(100),
-
1778 USD(100),
-
1779 IOUAmount{250, 0},
-
1780 std::nullopt,
-
1781 temMALFORMED},
-
1782 {1'000,
-
1783 XRP(100),
-
1784 USD(100),
-
1785 std::nullopt,
-
1786 std::nullopt,
-
1787 temMALFORMED},
-
1788 {std::nullopt,
-
1789 XRP(100),
-
1790 USD(100),
-
1791 std::nullopt,
-
1792 tfWithdrawAll,
-
1793 temMALFORMED}};
-
1794 for (auto const& it : invalidOptions)
-
1795 {
-
1796 ammAlice.withdraw(
-
1797 alice,
-
1798 std::get<0>(it),
-
1799 std::get<1>(it),
-
1800 std::get<2>(it),
-
1801 std::get<3>(it),
-
1802 std::get<4>(it),
-
1803 std::nullopt,
-
1804 std::nullopt,
-
1805 ter(std::get<5>(it)));
-
1806 }
-
1807
-
1808 // Invalid tokens
-
1809 ammAlice.withdraw(
-
1810 alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
-
1811 ammAlice.withdraw(
-
1812 alice,
-
1813 IOUAmount{-1},
-
1814 std::nullopt,
-
1815 std::nullopt,
-
1816 ter(temBAD_AMM_TOKENS));
-
1817
-
1818 // Mismatched token, invalid Asset1Out issue
-
1819 ammAlice.withdraw(
-
1820 alice,
-
1821 GBP(100),
-
1822 std::nullopt,
-
1823 std::nullopt,
-
1824 ter(temBAD_AMM_TOKENS));
-
1825
-
1826 // Mismatched token, invalid Asset2Out issue
-
1827 ammAlice.withdraw(
-
1828 alice,
-
1829 USD(100),
-
1830 GBP(100),
-
1831 std::nullopt,
-
1832 ter(temBAD_AMM_TOKENS));
-
1833
-
1834 // Mismatched token, Asset1Out.issue == Asset2Out.issue
-
1835 ammAlice.withdraw(
-
1836 alice,
-
1837 USD(100),
-
1838 USD(100),
-
1839 std::nullopt,
-
1840 ter(temBAD_AMM_TOKENS));
-
1841
-
1842 // Invalid amount value
-
1843 ammAlice.withdraw(
-
1844 alice, USD(0), std::nullopt, std::nullopt, ter(temBAD_AMOUNT));
-
1845 ammAlice.withdraw(
-
1846 alice,
-
1847 USD(-100),
-
1848 std::nullopt,
-
1849 std::nullopt,
-
1850 ter(temBAD_AMOUNT));
-
1851 ammAlice.withdraw(
-
1852 alice,
-
1853 USD(10),
-
1854 std::nullopt,
-
1855 IOUAmount{-1},
-
1856 ter(temBAD_AMOUNT));
-
1857
-
1858 // Invalid amount/token value, withdraw all tokens from one side
-
1859 // of the pool.
-
1860 ammAlice.withdraw(
-
1861 alice,
-
1862 USD(10'000),
-
1863 std::nullopt,
-
1864 std::nullopt,
-
1865 ter(tecAMM_BALANCE));
-
1866 ammAlice.withdraw(
-
1867 alice,
-
1868 XRP(10'000),
-
1869 std::nullopt,
-
1870 std::nullopt,
-
1871 ter(tecAMM_BALANCE));
-
1872 ammAlice.withdraw(
-
1873 alice,
-
1874 std::nullopt,
-
1875 USD(0),
-
1876 std::nullopt,
-
1877 std::nullopt,
-
1878 tfOneAssetWithdrawAll,
+
1772 std::nullopt,
+
1773 temMALFORMED},
+
1774 {std::nullopt,
+
1775 std::nullopt,
+
1776 std::nullopt,
+
1777 std::nullopt,
+
1778 tfSingleAsset | tfTwoAsset,
+
1779 temMALFORMED},
+
1780 {1'000,
+
1781 std::nullopt,
+
1782 std::nullopt,
+
1783 std::nullopt,
+
1784 tfWithdrawAll,
+
1785 temMALFORMED},
+
1786 {std::nullopt,
+
1787 USD(0),
+
1788 XRP(100),
+
1789 std::nullopt,
+
1790 tfWithdrawAll | tfLPToken,
+
1791 temMALFORMED},
+
1792 {std::nullopt,
+
1793 std::nullopt,
+
1794 USD(100),
+
1795 std::nullopt,
+
1796 tfWithdrawAll,
+
1797 temMALFORMED},
+
1798 {std::nullopt,
+
1799 std::nullopt,
+
1800 std::nullopt,
+
1801 std::nullopt,
+
1802 tfWithdrawAll | tfOneAssetWithdrawAll,
+
1803 temMALFORMED},
+
1804 {std::nullopt,
+
1805 USD(100),
+
1806 std::nullopt,
+
1807 std::nullopt,
+
1808 tfWithdrawAll,
+
1809 temMALFORMED},
+
1810 {std::nullopt,
+
1811 std::nullopt,
+
1812 std::nullopt,
+
1813 std::nullopt,
+
1814 tfOneAssetWithdrawAll,
+
1815 temMALFORMED},
+
1816 {1'000,
+
1817 std::nullopt,
+
1818 USD(100),
+
1819 std::nullopt,
+
1820 std::nullopt,
+
1821 temMALFORMED},
+
1822 {std::nullopt,
+
1823 std::nullopt,
+
1824 std::nullopt,
+
1825 IOUAmount{250, 0},
+
1826 tfWithdrawAll,
+
1827 temMALFORMED},
+
1828 {1'000,
+
1829 std::nullopt,
+
1830 std::nullopt,
+
1831 IOUAmount{250, 0},
+
1832 std::nullopt,
+
1833 temMALFORMED},
+
1834 {std::nullopt,
+
1835 std::nullopt,
+
1836 USD(100),
+
1837 IOUAmount{250, 0},
+
1838 std::nullopt,
+
1839 temMALFORMED},
+
1840 {std::nullopt,
+
1841 XRP(100),
+
1842 USD(100),
+
1843 IOUAmount{250, 0},
+
1844 std::nullopt,
+
1845 temMALFORMED},
+
1846 {1'000,
+
1847 XRP(100),
+
1848 USD(100),
+
1849 std::nullopt,
+
1850 std::nullopt,
+
1851 temMALFORMED},
+
1852 {std::nullopt,
+
1853 XRP(100),
+
1854 USD(100),
+
1855 std::nullopt,
+
1856 tfWithdrawAll,
+
1857 temMALFORMED}};
+
1858 for (auto const& it : invalidOptions)
+
1859 {
+
1860 ammAlice.withdraw(
+
1861 alice,
+
1862 std::get<0>(it),
+
1863 std::get<1>(it),
+
1864 std::get<2>(it),
+
1865 std::get<3>(it),
+
1866 std::get<4>(it),
+
1867 std::nullopt,
+
1868 std::nullopt,
+
1869 ter(std::get<5>(it)));
+
1870 }
+
1871
+
1872 // Invalid tokens
+
1873 ammAlice.withdraw(
+
1874 alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
+
1875 ammAlice.withdraw(
+
1876 alice,
+
1877 IOUAmount{-1},
+
1878 std::nullopt,
1879 std::nullopt,
-
1880 std::nullopt,
-
1881 ter(tecAMM_BALANCE));
-
1882
-
1883 // Bad currency
-
1884 ammAlice.withdraw(
-
1885 alice,
-
1886 BAD(100),
+
1880 ter(temBAD_AMM_TOKENS));
+
1881
+
1882 // Mismatched token, invalid Asset1Out issue
+
1883 ammAlice.withdraw(
+
1884 alice,
+
1885 GBP(100),
+
1886 std::nullopt,
1887 std::nullopt,
-
1888 std::nullopt,
-
1889 ter(temBAD_CURRENCY));
-
1890
-
1891 // Invalid Account
-
1892 Account bad("bad");
-
1893 env.memoize(bad);
-
1894 ammAlice.withdraw(
-
1895 bad,
-
1896 1'000'000,
-
1897 std::nullopt,
-
1898 std::nullopt,
-
1899 std::nullopt,
-
1900 std::nullopt,
-
1901 std::nullopt,
-
1902 seq(1),
-
1903 ter(terNO_ACCOUNT));
-
1904
-
1905 // Invalid AMM
-
1906 ammAlice.withdraw(
-
1907 alice,
-
1908 1'000,
-
1909 std::nullopt,
-
1910 std::nullopt,
-
1911 std::nullopt,
+
1888 ter(temBAD_AMM_TOKENS));
+
1889
+
1890 // Mismatched token, invalid Asset2Out issue
+
1891 ammAlice.withdraw(
+
1892 alice,
+
1893 USD(100),
+
1894 GBP(100),
+
1895 std::nullopt,
+
1896 ter(temBAD_AMM_TOKENS));
+
1897
+
1898 // Mismatched token, Asset1Out.issue == Asset2Out.issue
+
1899 ammAlice.withdraw(
+
1900 alice,
+
1901 USD(100),
+
1902 USD(100),
+
1903 std::nullopt,
+
1904 ter(temBAD_AMM_TOKENS));
+
1905
+
1906 // Invalid amount value
+
1907 ammAlice.withdraw(
+
1908 alice, USD(0), std::nullopt, std::nullopt, ter(temBAD_AMOUNT));
+
1909 ammAlice.withdraw(
+
1910 alice,
+
1911 USD(-100),
1912 std::nullopt,
-
1913 {{USD, GBP}},
-
1914 std::nullopt,
-
1915 ter(terNO_AMM));
-
1916
-
1917 // Carol is not a Liquidity Provider
-
1918 ammAlice.withdraw(
-
1919 carol, 10'000, std::nullopt, std::nullopt, ter(tecAMM_BALANCE));
-
1920
-
1921 // Withdraw entire one side of the pool.
-
1922 // Equal withdraw but due to XRP precision limit,
-
1923 // this results in full withdraw of XRP pool only,
-
1924 // while leaving a tiny amount in USD pool.
-
1925 ammAlice.withdraw(
-
1926 alice,
-
1927 IOUAmount{9'999'999'9999, -4},
+
1913 std::nullopt,
+
1914 ter(temBAD_AMOUNT));
+
1915 ammAlice.withdraw(
+
1916 alice,
+
1917 USD(10),
+
1918 std::nullopt,
+
1919 IOUAmount{-1},
+
1920 ter(temBAD_AMOUNT));
+
1921
+
1922 // Invalid amount/token value, withdraw all tokens from one side
+
1923 // of the pool.
+
1924 ammAlice.withdraw(
+
1925 alice,
+
1926 USD(10'000),
+
1927 std::nullopt,
1928 std::nullopt,
-
1929 std::nullopt,
-
1930 ter(tecAMM_BALANCE));
-
1931 // Withdrawing from one side.
-
1932 // XRP by tokens
-
1933 ammAlice.withdraw(
-
1934 alice,
-
1935 IOUAmount(9'999'999'9999, -4),
-
1936 XRP(0),
-
1937 std::nullopt,
-
1938 ter(tecAMM_BALANCE));
-
1939 // USD by tokens
-
1940 ammAlice.withdraw(
-
1941 alice,
-
1942 IOUAmount(9'999'999'9, -1),
-
1943 USD(0),
+
1929 ter(tecAMM_BALANCE));
+
1930 ammAlice.withdraw(
+
1931 alice,
+
1932 XRP(10'000),
+
1933 std::nullopt,
+
1934 std::nullopt,
+
1935 ter(tecAMM_BALANCE));
+
1936 ammAlice.withdraw(
+
1937 alice,
+
1938 std::nullopt,
+
1939 USD(0),
+
1940 std::nullopt,
+
1941 std::nullopt,
+
1942 tfOneAssetWithdrawAll,
+
1943 std::nullopt,
1944 std::nullopt,
1945 ter(tecAMM_BALANCE));
-
1946 // XRP
-
1947 ammAlice.withdraw(
-
1948 alice,
-
1949 XRP(10'000),
-
1950 std::nullopt,
+
1946
+
1947 // Bad currency
+
1948 ammAlice.withdraw(
+
1949 alice,
+
1950 BAD(100),
1951 std::nullopt,
-
1952 ter(tecAMM_BALANCE));
-
1953 // USD
-
1954 ammAlice.withdraw(
-
1955 alice,
-
1956 STAmount{USD, UINT64_C(9'999'9999999999999), -13},
-
1957 std::nullopt,
-
1958 std::nullopt,
-
1959 ter(tecAMM_BALANCE));
-
1960 });
-
1961
-
1962 // Invalid AMM
-
1963 testAMM([&](AMM& ammAlice, Env& env) {
-
1964 ammAlice.withdrawAll(alice);
-
1965 ammAlice.withdraw(
-
1966 alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM));
-
1967 });
+
1952 std::nullopt,
+
1953 ter(temBAD_CURRENCY));
+
1954
+
1955 // Invalid Account
+
1956 Account bad("bad");
+
1957 env.memoize(bad);
+
1958 ammAlice.withdraw(
+
1959 bad,
+
1960 1'000'000,
+
1961 std::nullopt,
+
1962 std::nullopt,
+
1963 std::nullopt,
+
1964 std::nullopt,
+
1965 std::nullopt,
+
1966 seq(1),
+
1967 ter(terNO_ACCOUNT));
1968
-
1969 // Globally frozen asset
-
1970 testAMM([&](AMM& ammAlice, Env& env) {
-
1971 env(fset(gw, asfGlobalFreeze));
-
1972 env.close();
-
1973 // Can withdraw non-frozen token
-
1974 ammAlice.withdraw(alice, XRP(100));
-
1975 ammAlice.withdraw(
-
1976 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
-
1977 ammAlice.withdraw(
-
1978 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
-
1979 });
+
1969 // Invalid AMM
+
1970 ammAlice.withdraw(
+
1971 alice,
+
1972 1'000,
+
1973 std::nullopt,
+
1974 std::nullopt,
+
1975 std::nullopt,
+
1976 std::nullopt,
+
1977 {{USD, GBP}},
+
1978 std::nullopt,
+
1979 ter(terNO_AMM));
1980
-
1981 // Individually frozen (AMM) account
-
1982 testAMM([&](AMM& ammAlice, Env& env) {
-
1983 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
1984 env.close();
-
1985 // Can withdraw non-frozen token
-
1986 ammAlice.withdraw(alice, XRP(100));
+
1981 // Carol is not a Liquidity Provider
+
1982 ammAlice.withdraw(
+
1983 carol, 10'000, std::nullopt, std::nullopt, ter(tecAMM_BALANCE));
+
1984
+
1985 // Withdrawing from one side.
+
1986 // XRP by tokens
1987 ammAlice.withdraw(
-
1988 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
-
1989 ammAlice.withdraw(
-
1990 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
-
1991 env(trust(gw, alice["USD"](0), tfClearFreeze));
-
1992 // Individually frozen AMM
-
1993 env(trust(
-
1994 gw,
-
1995 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
-
1996 tfSetFreeze));
-
1997 // Can withdraw non-frozen token
-
1998 ammAlice.withdraw(alice, XRP(100));
-
1999 ammAlice.withdraw(
-
2000 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
+
1988 alice,
+
1989 IOUAmount(9'999'999'9999, -4),
+
1990 XRP(0),
+
1991 std::nullopt,
+
1992 ter(tecAMM_BALANCE));
+
1993 // USD by tokens
+
1994 ammAlice.withdraw(
+
1995 alice,
+
1996 IOUAmount(9'999'999'9, -1),
+
1997 USD(0),
+
1998 std::nullopt,
+
1999 ter(tecAMM_BALANCE));
+
2000 // XRP
2001 ammAlice.withdraw(
-
2002 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
-
2003 });
-
2004
-
2005 // Carol withdraws more than she owns
-
2006 testAMM([&](AMM& ammAlice, Env&) {
-
2007 // Single deposit of 100000 worth of tokens,
-
2008 // which is 10% of the pool. Carol is LP now.
-
2009 ammAlice.deposit(carol, 1'000'000);
-
2010 BEAST_EXPECT(ammAlice.expectBalances(
-
2011 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
2012
-
2013 ammAlice.withdraw(
-
2014 carol,
-
2015 2'000'000,
-
2016 std::nullopt,
-
2017 std::nullopt,
-
2018 ter(tecAMM_INVALID_TOKENS));
-
2019 BEAST_EXPECT(ammAlice.expectBalances(
-
2020 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
2021 });
-
2022
-
2023 // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
-
2024 // to withdraw are 0.
-
2025 testAMM([&](AMM& ammAlice, Env&) {
-
2026 ammAlice.deposit(carol, 1'000'000);
-
2027 ammAlice.withdraw(
-
2028 carol,
-
2029 USD(100),
-
2030 std::nullopt,
-
2031 IOUAmount{500, 0},
-
2032 ter(tecAMM_FAILED));
-
2033 });
-
2034
-
2035 // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
-
2036 // to withdraw are greater than the LP shares.
-
2037 testAMM([&](AMM& ammAlice, Env&) {
-
2038 ammAlice.deposit(carol, 1'000'000);
-
2039 ammAlice.withdraw(
-
2040 carol,
-
2041 USD(100),
-
2042 std::nullopt,
-
2043 IOUAmount{600, 0},
-
2044 ter(tecAMM_INVALID_TOKENS));
-
2045 });
-
2046
-
2047 // Withdraw with EPrice limit. Fails to withdraw, amount1
-
2048 // to withdraw is less than 1700USD.
-
2049 testAMM([&](AMM& ammAlice, Env&) {
-
2050 ammAlice.deposit(carol, 1'000'000);
-
2051 ammAlice.withdraw(
-
2052 carol,
-
2053 USD(1'700),
-
2054 std::nullopt,
-
2055 IOUAmount{520, 0},
-
2056 ter(tecAMM_FAILED));
-
2057 });
-
2058
-
2059 // Deposit/Withdraw the same amount with the trading fee
-
2060 testAMM(
-
2061 [&](AMM& ammAlice, Env&) {
-
2062 ammAlice.deposit(carol, USD(1'000));
-
2063 ammAlice.withdraw(
-
2064 carol,
-
2065 USD(1'000),
-
2066 std::nullopt,
-
2067 std::nullopt,
-
2068 ter(tecAMM_INVALID_TOKENS));
-
2069 },
-
2070 std::nullopt,
-
2071 1'000);
-
2072 testAMM(
-
2073 [&](AMM& ammAlice, Env&) {
-
2074 ammAlice.deposit(carol, XRP(1'000));
-
2075 ammAlice.withdraw(
-
2076 carol,
-
2077 XRP(1'000),
-
2078 std::nullopt,
-
2079 std::nullopt,
-
2080 ter(tecAMM_INVALID_TOKENS));
-
2081 },
-
2082 std::nullopt,
-
2083 1'000);
-
2084
-
2085 // Deposit/Withdraw the same amount fails due to the tokens adjustment
-
2086 testAMM([&](AMM& ammAlice, Env&) {
-
2087 ammAlice.deposit(carol, STAmount{USD, 1, -6});
-
2088 ammAlice.withdraw(
-
2089 carol,
-
2090 STAmount{USD, 1, -6},
-
2091 std::nullopt,
-
2092 std::nullopt,
-
2093 ter(tecAMM_INVALID_TOKENS));
-
2094 });
-
2095
-
2096 // Withdraw close to one side of the pool. Account's LP tokens
-
2097 // are rounded to all LP tokens.
-
2098 testAMM([&](AMM& ammAlice, Env&) {
-
2099 ammAlice.withdraw(
-
2100 alice,
-
2101 STAmount{USD, UINT64_C(9'999'999999999999), -12},
-
2102 std::nullopt,
-
2103 std::nullopt,
-
2104 ter(tecAMM_BALANCE));
-
2105 });
-
2106
-
2107 // Tiny withdraw
-
2108 testAMM([&](AMM& ammAlice, Env&) {
-
2109 // XRP amount to withdraw is 0
-
2110 ammAlice.withdraw(
-
2111 alice,
-
2112 IOUAmount{1, -5},
-
2113 std::nullopt,
-
2114 std::nullopt,
-
2115 ter(tecAMM_FAILED));
-
2116 // Calculated tokens to withdraw are 0
-
2117 ammAlice.withdraw(
-
2118 alice,
-
2119 std::nullopt,
-
2120 STAmount{USD, 1, -11},
+
2002 alice,
+
2003 XRP(10'000),
+
2004 std::nullopt,
+
2005 std::nullopt,
+
2006 ter(tecAMM_BALANCE));
+
2007 // USD
+
2008 ammAlice.withdraw(
+
2009 alice,
+
2010 STAmount{USD, UINT64_C(9'999'9999999999999), -13},
+
2011 std::nullopt,
+
2012 std::nullopt,
+
2013 ter(tecAMM_BALANCE));
+
2014 });
+
2015
+
2016 testAMM(
+
2017 [&](AMM& ammAlice, Env& env) {
+
2018 // Withdraw entire one side of the pool.
+
2019 // Pre-amendment:
+
2020 // Equal withdraw but due to XRP rounding
+
2021 // this results in full withdraw of XRP pool only,
+
2022 // while leaving a tiny amount in USD pool.
+
2023 // Post-amendment:
+
2024 // Most of the pool is withdrawn with remaining tiny amounts
+
2025 auto err = env.enabled(fixAMMv1_3) ? ter(tesSUCCESS)
+
2026 : ter(tecAMM_BALANCE);
+
2027 ammAlice.withdraw(
+
2028 alice,
+
2029 IOUAmount{9'999'999'9999, -4},
+
2030 std::nullopt,
+
2031 std::nullopt,
+
2032 err);
+
2033 if (env.enabled(fixAMMv1_3))
+
2034 BEAST_EXPECT(ammAlice.expectBalances(
+
2035 XRPAmount(1), STAmount{USD, 1, -7}, IOUAmount{1, -4}));
+
2036 },
+
2037 std::nullopt,
+
2038 0,
+
2039 std::nullopt,
+
2040 {all, all - fixAMMv1_3});
+
2041
+
2042 testAMM(
+
2043 [&](AMM& ammAlice, Env& env) {
+
2044 // Similar to above with even smaller remaining amount
+
2045 // is it ok that the pool is unbalanced?
+
2046 // Withdraw entire one side of the pool.
+
2047 // Equal withdraw but due to XRP precision limit,
+
2048 // this results in full withdraw of XRP pool only,
+
2049 // while leaving a tiny amount in USD pool.
+
2050 auto err = env.enabled(fixAMMv1_3) ? ter(tesSUCCESS)
+
2051 : ter(tecAMM_BALANCE);
+
2052 ammAlice.withdraw(
+
2053 alice,
+
2054 IOUAmount{9'999'999'999999999, -9},
+
2055 std::nullopt,
+
2056 std::nullopt,
+
2057 err);
+
2058 if (env.enabled(fixAMMv1_3))
+
2059 BEAST_EXPECT(ammAlice.expectBalances(
+
2060 XRPAmount(1), STAmount{USD, 1, -11}, IOUAmount{1, -8}));
+
2061 },
+
2062 std::nullopt,
+
2063 0,
+
2064 std::nullopt,
+
2065 {all, all - fixAMMv1_3});
+
2066
+
2067 // Invalid AMM
+
2068 testAMM([&](AMM& ammAlice, Env& env) {
+
2069 ammAlice.withdrawAll(alice);
+
2070 ammAlice.withdraw(
+
2071 alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM));
+
2072 });
+
2073
+
2074 // Globally frozen asset
+
2075 testAMM([&](AMM& ammAlice, Env& env) {
+
2076 env(fset(gw, asfGlobalFreeze));
+
2077 env.close();
+
2078 // Can withdraw non-frozen token
+
2079 ammAlice.withdraw(alice, XRP(100));
+
2080 ammAlice.withdraw(
+
2081 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
+
2082 ammAlice.withdraw(
+
2083 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
+
2084 });
+
2085
+
2086 // Individually frozen (AMM) account
+
2087 testAMM([&](AMM& ammAlice, Env& env) {
+
2088 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
2089 env.close();
+
2090 // Can withdraw non-frozen token
+
2091 ammAlice.withdraw(alice, XRP(100));
+
2092 ammAlice.withdraw(
+
2093 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
+
2094 ammAlice.withdraw(
+
2095 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
+
2096 env(trust(gw, alice["USD"](0), tfClearFreeze));
+
2097 // Individually frozen AMM
+
2098 env(trust(
+
2099 gw,
+
2100 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
+
2101 tfSetFreeze));
+
2102 // Can withdraw non-frozen token
+
2103 ammAlice.withdraw(alice, XRP(100));
+
2104 ammAlice.withdraw(
+
2105 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
+
2106 ammAlice.withdraw(
+
2107 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
+
2108 });
+
2109
+
2110 // Carol withdraws more than she owns
+
2111 testAMM([&](AMM& ammAlice, Env&) {
+
2112 // Single deposit of 100000 worth of tokens,
+
2113 // which is 10% of the pool. Carol is LP now.
+
2114 ammAlice.deposit(carol, 1'000'000);
+
2115 BEAST_EXPECT(ammAlice.expectBalances(
+
2116 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
2117
+
2118 ammAlice.withdraw(
+
2119 carol,
+
2120 2'000'000,
2121 std::nullopt,
-
2122 ter(tecAMM_INVALID_TOKENS));
-
2123 ammAlice.deposit(carol, STAmount{USD, 1, -10});
-
2124 ammAlice.withdraw(
-
2125 carol,
-
2126 std::nullopt,
-
2127 STAmount{USD, 1, -9},
-
2128 std::nullopt,
-
2129 ter(tecAMM_INVALID_TOKENS));
-
2130 ammAlice.withdraw(
-
2131 carol,
-
2132 std::nullopt,
-
2133 XRPAmount{1},
-
2134 std::nullopt,
-
2135 ter(tecAMM_INVALID_TOKENS));
-
2136 });
-
2137 }
-
2138
-
2139 void
-
2140 testWithdraw()
-
2141 {
-
2142 testcase("Withdraw");
+
2122 std::nullopt,
+
2123 ter(tecAMM_INVALID_TOKENS));
+
2124 BEAST_EXPECT(ammAlice.expectBalances(
+
2125 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
2126 });
+
2127
+
2128 // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
+
2129 // to withdraw are 0.
+
2130 testAMM(
+
2131 [&](AMM& ammAlice, Env& env) {
+
2132 ammAlice.deposit(carol, 1'000'000);
+
2133 auto const err = env.enabled(fixAMMv1_3)
+
2134 ? ter(tecAMM_INVALID_TOKENS)
+
2135 : ter(tecAMM_FAILED);
+
2136 ammAlice.withdraw(
+
2137 carol, USD(100), std::nullopt, IOUAmount{500, 0}, err);
+
2138 },
+
2139 std::nullopt,
+
2140 0,
+
2141 std::nullopt,
+
2142 {all, all - fixAMMv1_3});
2143
-
2144 using namespace jtx;
-
2145
-
2146 // Equal withdrawal by Carol: 1000000 of tokens, 10% of the current
-
2147 // pool
-
2148 testAMM([&](AMM& ammAlice, Env& env) {
-
2149 auto const baseFee = env.current()->fees().base.drops();
-
2150 // Single deposit of 100000 worth of tokens,
-
2151 // which is 10% of the pool. Carol is LP now.
-
2152 ammAlice.deposit(carol, 1'000'000);
-
2153 BEAST_EXPECT(ammAlice.expectBalances(
-
2154 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
2155 BEAST_EXPECT(
-
2156 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
-
2157 // 30,000 less deposited 1,000
-
2158 BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
-
2159 // 30,000 less deposited 1,000 and 10 drops tx fee
-
2160 BEAST_EXPECT(expectLedgerEntryRoot(
-
2161 env, carol, XRPAmount{29'000'000'000 - baseFee}));
-
2162
-
2163 // Carol withdraws all tokens
-
2164 ammAlice.withdraw(carol, 1'000'000);
-
2165 BEAST_EXPECT(
-
2166 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
-
2167 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
2168 BEAST_EXPECT(expectLedgerEntryRoot(
-
2169 env, carol, XRPAmount{30'000'000'000 - 2 * baseFee}));
-
2170 });
-
2171
-
2172 // Equal withdrawal by tokens 1000000, 10%
-
2173 // of the current pool
-
2174 testAMM([&](AMM& ammAlice, Env&) {
-
2175 ammAlice.withdraw(alice, 1'000'000);
-
2176 BEAST_EXPECT(ammAlice.expectBalances(
-
2177 XRP(9'000), USD(9'000), IOUAmount{9'000'000, 0}));
-
2178 });
-
2179
-
2180 // Equal withdrawal with a limit. Withdraw XRP200.
-
2181 // If proportional withdraw of USD is less than 100
-
2182 // then withdraw that amount, otherwise withdraw USD100
-
2183 // and proportionally withdraw XRP. It's the latter
-
2184 // in this case - XRP100/USD100.
-
2185 testAMM([&](AMM& ammAlice, Env&) {
-
2186 ammAlice.withdraw(alice, XRP(200), USD(100));
-
2187 BEAST_EXPECT(ammAlice.expectBalances(
-
2188 XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
-
2189 });
-
2190
-
2191 // Equal withdrawal with a limit. XRP100/USD100.
-
2192 testAMM([&](AMM& ammAlice, Env&) {
-
2193 ammAlice.withdraw(alice, XRP(100), USD(200));
-
2194 BEAST_EXPECT(ammAlice.expectBalances(
-
2195 XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
-
2196 });
-
2197
-
2198 // Single withdrawal by amount XRP1000
-
2199 testAMM([&](AMM& ammAlice, Env&) {
-
2200 ammAlice.withdraw(alice, XRP(1'000));
-
2201 BEAST_EXPECT(ammAlice.expectBalances(
-
2202 XRP(9'000), USD(10'000), IOUAmount{9'486'832'98050514, -8}));
+
2144 // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
+
2145 // to withdraw are greater than the LP shares.
+
2146 testAMM([&](AMM& ammAlice, Env&) {
+
2147 ammAlice.deposit(carol, 1'000'000);
+
2148 ammAlice.withdraw(
+
2149 carol,
+
2150 USD(100),
+
2151 std::nullopt,
+
2152 IOUAmount{600, 0},
+
2153 ter(tecAMM_INVALID_TOKENS));
+
2154 });
+
2155
+
2156 // Withdraw with EPrice limit. Fails to withdraw, amount1
+
2157 // to withdraw is less than 1700USD.
+
2158 testAMM([&](AMM& ammAlice, Env&) {
+
2159 ammAlice.deposit(carol, 1'000'000);
+
2160 ammAlice.withdraw(
+
2161 carol,
+
2162 USD(1'700),
+
2163 std::nullopt,
+
2164 IOUAmount{520, 0},
+
2165 ter(tecAMM_FAILED));
+
2166 });
+
2167
+
2168 // Deposit/Withdraw the same amount with the trading fee
+
2169 testAMM(
+
2170 [&](AMM& ammAlice, Env&) {
+
2171 ammAlice.deposit(carol, USD(1'000));
+
2172 ammAlice.withdraw(
+
2173 carol,
+
2174 USD(1'000),
+
2175 std::nullopt,
+
2176 std::nullopt,
+
2177 ter(tecAMM_INVALID_TOKENS));
+
2178 },
+
2179 std::nullopt,
+
2180 1'000);
+
2181 testAMM(
+
2182 [&](AMM& ammAlice, Env&) {
+
2183 ammAlice.deposit(carol, XRP(1'000));
+
2184 ammAlice.withdraw(
+
2185 carol,
+
2186 XRP(1'000),
+
2187 std::nullopt,
+
2188 std::nullopt,
+
2189 ter(tecAMM_INVALID_TOKENS));
+
2190 },
+
2191 std::nullopt,
+
2192 1'000);
+
2193
+
2194 // Deposit/Withdraw the same amount fails due to the tokens adjustment
+
2195 testAMM([&](AMM& ammAlice, Env&) {
+
2196 ammAlice.deposit(carol, STAmount{USD, 1, -6});
+
2197 ammAlice.withdraw(
+
2198 carol,
+
2199 STAmount{USD, 1, -6},
+
2200 std::nullopt,
+
2201 std::nullopt,
+
2202 ter(tecAMM_INVALID_TOKENS));
2203 });
2204
-
2205 // Single withdrawal by tokens 10000.
-
2206 testAMM([&](AMM& ammAlice, Env&) {
-
2207 ammAlice.withdraw(alice, 10'000, USD(0));
-
2208 BEAST_EXPECT(ammAlice.expectBalances(
-
2209 XRP(10'000), USD(9980.01), IOUAmount{9'990'000, 0}));
-
2210 });
-
2211
-
2212 // Withdraw all tokens.
-
2213 testAMM([&](AMM& ammAlice, Env& env) {
-
2214 env(trust(carol, STAmount{ammAlice.lptIssue(), 10'000}));
-
2215 // Can SetTrust only for AMM LP tokens
-
2216 env(trust(
-
2217 carol,
-
2218 STAmount{
-
2219 Issue{EUR.currency, ammAlice.ammAccount()}, 10'000}),
-
2220 ter(tecNO_PERMISSION));
-
2221 env.close();
-
2222 ammAlice.withdrawAll(alice);
-
2223 BEAST_EXPECT(!ammAlice.ammExists());
-
2224
-
2225 BEAST_EXPECT(!env.le(keylet::ownerDir(ammAlice.ammAccount())));
-
2226
-
2227 // Can create AMM for the XRP/USD pair
-
2228 AMM ammCarol(env, carol, XRP(10'000), USD(10'000));
-
2229 BEAST_EXPECT(ammCarol.expectBalances(
-
2230 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
-
2231 });
-
2232
-
2233 // Single deposit 1000USD, withdraw all tokens in USD
-
2234 testAMM([&](AMM& ammAlice, Env& env) {
-
2235 ammAlice.deposit(carol, USD(1'000));
-
2236 ammAlice.withdrawAll(carol, USD(0));
-
2237 BEAST_EXPECT(ammAlice.expectBalances(
-
2238 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
-
2239 BEAST_EXPECT(
-
2240 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
-
2241 });
-
2242
-
2243 // Single deposit 1000USD, withdraw all tokens in XRP
-
2244 testAMM([&](AMM& ammAlice, Env&) {
-
2245 ammAlice.deposit(carol, USD(1'000));
-
2246 ammAlice.withdrawAll(carol, XRP(0));
-
2247 BEAST_EXPECT(ammAlice.expectBalances(
-
2248 XRPAmount(9'090'909'091),
-
2249 STAmount{USD, UINT64_C(10'999'99999999999), -11},
-
2250 IOUAmount{10'000'000, 0}));
-
2251 });
-
2252
-
2253 // Single deposit/withdraw by the same account
-
2254 testAMM([&](AMM& ammAlice, Env&) {
-
2255 // Since a smaller amount might be deposited due to
-
2256 // the lp tokens adjustment, withdrawing by tokens
-
2257 // is generally preferred to withdrawing by amount.
-
2258 auto lpTokens = ammAlice.deposit(carol, USD(1'000));
-
2259 ammAlice.withdraw(carol, lpTokens, USD(0));
-
2260 lpTokens = ammAlice.deposit(carol, STAmount(USD, 1, -6));
-
2261 ammAlice.withdraw(carol, lpTokens, USD(0));
-
2262 lpTokens = ammAlice.deposit(carol, XRPAmount(1));
-
2263 ammAlice.withdraw(carol, lpTokens, XRPAmount(0));
-
2264 BEAST_EXPECT(ammAlice.expectBalances(
-
2265 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
2266 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
2267 });
+
2205 // Withdraw close to one side of the pool. Account's LP tokens
+
2206 // are rounded to all LP tokens.
+
2207 testAMM(
+
2208 [&](AMM& ammAlice, Env& env) {
+
2209 auto const err = env.enabled(fixAMMv1_3)
+
2210 ? ter(tecINVARIANT_FAILED)
+
2211 : ter(tecAMM_BALANCE);
+
2212 ammAlice.withdraw(
+
2213 alice,
+
2214 STAmount{USD, UINT64_C(9'999'999999999999), -12},
+
2215 std::nullopt,
+
2216 std::nullopt,
+
2217 err);
+
2218 },
+
2219 {.features = {all, all - fixAMMv1_3}, .noLog = true});
+
2220
+
2221 // Tiny withdraw
+
2222 testAMM([&](AMM& ammAlice, Env&) {
+
2223 // XRP amount to withdraw is 0
+
2224 ammAlice.withdraw(
+
2225 alice,
+
2226 IOUAmount{1, -5},
+
2227 std::nullopt,
+
2228 std::nullopt,
+
2229 ter(tecAMM_FAILED));
+
2230 // Calculated tokens to withdraw are 0
+
2231 ammAlice.withdraw(
+
2232 alice,
+
2233 std::nullopt,
+
2234 STAmount{USD, 1, -11},
+
2235 std::nullopt,
+
2236 ter(tecAMM_INVALID_TOKENS));
+
2237 ammAlice.deposit(carol, STAmount{USD, 1, -10});
+
2238 ammAlice.withdraw(
+
2239 carol,
+
2240 std::nullopt,
+
2241 STAmount{USD, 1, -9},
+
2242 std::nullopt,
+
2243 ter(tecAMM_INVALID_TOKENS));
+
2244 ammAlice.withdraw(
+
2245 carol,
+
2246 std::nullopt,
+
2247 XRPAmount{1},
+
2248 std::nullopt,
+
2249 ter(tecAMM_INVALID_TOKENS));
+
2250 ammAlice.withdraw(WithdrawArg{
+
2251 .tokens = IOUAmount{1, -10},
+
2252 .err = ter(tecAMM_INVALID_TOKENS)});
+
2253 ammAlice.withdraw(WithdrawArg{
+
2254 .asset1Out = STAmount{USD, 1, -15},
+
2255 .asset2Out = XRPAmount{1},
+
2256 .err = ter(tecAMM_INVALID_TOKENS)});
+
2257 ammAlice.withdraw(WithdrawArg{
+
2258 .tokens = IOUAmount{1, -10},
+
2259 .asset1Out = STAmount{USD, 1, -15},
+
2260 .err = ter(tecAMM_INVALID_TOKENS)});
+
2261 });
+
2262 }
+
2263
+
2264 void
+
2265 testWithdraw()
+
2266 {
+
2267 testcase("Withdraw");
2268
-
2269 // Single deposit by different accounts and then withdraw
-
2270 // in reverse.
-
2271 testAMM([&](AMM& ammAlice, Env&) {
-
2272 auto const carolTokens = ammAlice.deposit(carol, USD(1'000));
-
2273 auto const aliceTokens = ammAlice.deposit(alice, USD(1'000));
-
2274 ammAlice.withdraw(alice, aliceTokens, USD(0));
-
2275 ammAlice.withdraw(carol, carolTokens, USD(0));
-
2276 BEAST_EXPECT(ammAlice.expectBalances(
-
2277 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
2278 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
2279 BEAST_EXPECT(ammAlice.expectLPTokens(alice, ammAlice.tokens()));
-
2280 });
-
2281
-
2282 // Equal deposit 10%, withdraw all tokens
-
2283 testAMM([&](AMM& ammAlice, Env&) {
-
2284 ammAlice.deposit(carol, 1'000'000);
-
2285 ammAlice.withdrawAll(carol);
-
2286 BEAST_EXPECT(ammAlice.expectBalances(
-
2287 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
-
2288 });
-
2289
-
2290 // Equal deposit 10%, withdraw all tokens in USD
-
2291 testAMM([&](AMM& ammAlice, Env&) {
-
2292 ammAlice.deposit(carol, 1'000'000);
-
2293 ammAlice.withdrawAll(carol, USD(0));
-
2294 BEAST_EXPECT(ammAlice.expectBalances(
-
2295 XRP(11'000),
-
2296 STAmount{USD, UINT64_C(9'090'909090909092), -12},
-
2297 IOUAmount{10'000'000, 0}));
-
2298 });
-
2299
-
2300 // Equal deposit 10%, withdraw all tokens in XRP
-
2301 testAMM([&](AMM& ammAlice, Env&) {
-
2302 ammAlice.deposit(carol, 1'000'000);
-
2303 ammAlice.withdrawAll(carol, XRP(0));
-
2304 BEAST_EXPECT(ammAlice.expectBalances(
-
2305 XRPAmount(9'090'909'091),
-
2306 USD(11'000),
-
2307 IOUAmount{10'000'000, 0}));
-
2308 });
-
2309
-
2310 auto const all = supported_amendments();
-
2311 // Withdraw with EPrice limit.
-
2312 testAMM(
-
2313 [&](AMM& ammAlice, Env& env) {
-
2314 ammAlice.deposit(carol, 1'000'000);
-
2315 ammAlice.withdraw(
-
2316 carol, USD(100), std::nullopt, IOUAmount{520, 0});
-
2317 if (!env.current()->rules().enabled(fixAMMv1_1))
-
2318 BEAST_EXPECT(
-
2319 ammAlice.expectBalances(
-
2320 XRPAmount(11'000'000'000),
-
2321 STAmount{USD, UINT64_C(9'372'781065088757), -12},
-
2322 IOUAmount{10'153'846'15384616, -8}) &&
-
2323 ammAlice.expectLPTokens(
-
2324 carol, IOUAmount{153'846'15384616, -8}));
-
2325 else
-
2326 BEAST_EXPECT(
-
2327 ammAlice.expectBalances(
-
2328 XRPAmount(11'000'000'000),
-
2329 STAmount{USD, UINT64_C(9'372'781065088769), -12},
-
2330 IOUAmount{10'153'846'15384616, -8}) &&
-
2331 ammAlice.expectLPTokens(
-
2332 carol, IOUAmount{153'846'15384616, -8}));
-
2333 ammAlice.withdrawAll(carol);
-
2334 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
2335 },
-
2336 std::nullopt,
-
2337 0,
-
2338 std::nullopt,
-
2339 {all, all - fixAMMv1_1});
-
2340
-
2341 // Withdraw with EPrice limit. AssetOut is 0.
-
2342 testAMM(
-
2343 [&](AMM& ammAlice, Env& env) {
-
2344 ammAlice.deposit(carol, 1'000'000);
-
2345 ammAlice.withdraw(
-
2346 carol, USD(0), std::nullopt, IOUAmount{520, 0});
-
2347 if (!env.current()->rules().enabled(fixAMMv1_1))
-
2348 BEAST_EXPECT(
-
2349 ammAlice.expectBalances(
-
2350 XRPAmount(11'000'000'000),
-
2351 STAmount{USD, UINT64_C(9'372'781065088757), -12},
-
2352 IOUAmount{10'153'846'15384616, -8}) &&
-
2353 ammAlice.expectLPTokens(
-
2354 carol, IOUAmount{153'846'15384616, -8}));
-
2355 else
-
2356 BEAST_EXPECT(
-
2357 ammAlice.expectBalances(
-
2358 XRPAmount(11'000'000'000),
-
2359 STAmount{USD, UINT64_C(9'372'781065088769), -12},
-
2360 IOUAmount{10'153'846'15384616, -8}) &&
-
2361 ammAlice.expectLPTokens(
-
2362 carol, IOUAmount{153'846'15384616, -8}));
-
2363 },
-
2364 std::nullopt,
-
2365 0,
-
2366 std::nullopt,
-
2367 {all, all - fixAMMv1_1});
-
2368
-
2369 // IOU to IOU + transfer fee
-
2370 {
-
2371 Env env{*this};
-
2372 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
-
2373 env(rate(gw, 1.25));
-
2374 env.close();
-
2375 // no transfer fee on create
-
2376 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
-
2377 BEAST_EXPECT(ammAlice.expectBalances(
-
2378 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
-
2379 BEAST_EXPECT(expectLine(env, alice, USD(0)));
-
2380 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
-
2381 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
-
2382 // no transfer fee on deposit
-
2383 ammAlice.deposit(carol, 10);
-
2384 BEAST_EXPECT(ammAlice.expectBalances(
-
2385 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
-
2386 BEAST_EXPECT(expectLine(env, carol, USD(0)));
-
2387 BEAST_EXPECT(expectLine(env, carol, BTC(0)));
-
2388 // no transfer fee on withdraw
-
2389 ammAlice.withdraw(carol, 10);
-
2390 BEAST_EXPECT(ammAlice.expectBalances(
-
2391 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
-
2392 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
-
2393 BEAST_EXPECT(expectLine(env, carol, USD(2'000)));
-
2394 BEAST_EXPECT(expectLine(env, carol, BTC(0.05)));
-
2395 }
-
2396
-
2397 // Tiny withdraw
-
2398 testAMM([&](AMM& ammAlice, Env&) {
-
2399 // By tokens
-
2400 ammAlice.withdraw(alice, IOUAmount{1, -3});
-
2401 BEAST_EXPECT(ammAlice.expectBalances(
-
2402 XRPAmount{9'999'999'999},
-
2403 STAmount{USD, UINT64_C(9'999'999999), -6},
-
2404 IOUAmount{9'999'999'999, -3}));
-
2405 });
-
2406 testAMM([&](AMM& ammAlice, Env&) {
-
2407 // Single XRP pool
-
2408 ammAlice.withdraw(alice, std::nullopt, XRPAmount{1});
-
2409 BEAST_EXPECT(ammAlice.expectBalances(
-
2410 XRPAmount{9'999'999'999},
-
2411 USD(10'000),
-
2412 IOUAmount{9'999'999'9995, -4}));
-
2413 });
-
2414 testAMM([&](AMM& ammAlice, Env&) {
-
2415 // Single USD pool
-
2416 ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
-
2417 BEAST_EXPECT(ammAlice.expectBalances(
-
2418 XRP(10'000),
-
2419 STAmount{USD, UINT64_C(9'999'9999999999), -10},
-
2420 IOUAmount{9'999'999'99999995, -8}));
-
2421 });
-
2422
-
2423 // Withdraw close to entire pool
-
2424 // Equal by tokens
-
2425 testAMM([&](AMM& ammAlice, Env&) {
-
2426 ammAlice.withdraw(alice, IOUAmount{9'999'999'999, -3});
-
2427 BEAST_EXPECT(ammAlice.expectBalances(
-
2428 XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
-
2429 });
-
2430 // USD by tokens
-
2431 testAMM([&](AMM& ammAlice, Env&) {
-
2432 ammAlice.withdraw(alice, IOUAmount{9'999'999}, USD(0));
-
2433 BEAST_EXPECT(ammAlice.expectBalances(
-
2434 XRP(10'000), STAmount{USD, 1, -10}, IOUAmount{1}));
-
2435 });
-
2436 // XRP by tokens
-
2437 testAMM([&](AMM& ammAlice, Env&) {
-
2438 ammAlice.withdraw(alice, IOUAmount{9'999'900}, XRP(0));
-
2439 BEAST_EXPECT(ammAlice.expectBalances(
-
2440 XRPAmount{1}, USD(10'000), IOUAmount{100}));
-
2441 });
-
2442 // USD
-
2443 testAMM([&](AMM& ammAlice, Env&) {
-
2444 ammAlice.withdraw(
-
2445 alice, STAmount{USD, UINT64_C(9'999'99999999999), -11});
-
2446 BEAST_EXPECT(ammAlice.expectBalances(
-
2447 XRP(10000), STAmount{USD, 1, -11}, IOUAmount{316227765, -9}));
+
2269 using namespace jtx;
+
2270 auto const all = supported_amendments();
+
2271
+
2272 // Equal withdrawal by Carol: 1000000 of tokens, 10% of the current
+
2273 // pool
+
2274 testAMM([&](AMM& ammAlice, Env& env) {
+
2275 auto const baseFee = env.current()->fees().base.drops();
+
2276 // Single deposit of 100000 worth of tokens,
+
2277 // which is 10% of the pool. Carol is LP now.
+
2278 ammAlice.deposit(carol, 1'000'000);
+
2279 BEAST_EXPECT(ammAlice.expectBalances(
+
2280 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
2281 BEAST_EXPECT(
+
2282 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
+
2283 // 30,000 less deposited 1,000
+
2284 BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
+
2285 // 30,000 less deposited 1,000 and 10 drops tx fee
+
2286 BEAST_EXPECT(expectLedgerEntryRoot(
+
2287 env, carol, XRPAmount{29'000'000'000 - baseFee}));
+
2288
+
2289 // Carol withdraws all tokens
+
2290 ammAlice.withdraw(carol, 1'000'000);
+
2291 BEAST_EXPECT(
+
2292 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
+
2293 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
2294 BEAST_EXPECT(expectLedgerEntryRoot(
+
2295 env, carol, XRPAmount{30'000'000'000 - 2 * baseFee}));
+
2296 });
+
2297
+
2298 // Equal withdrawal by tokens 1000000, 10%
+
2299 // of the current pool
+
2300 testAMM([&](AMM& ammAlice, Env&) {
+
2301 ammAlice.withdraw(alice, 1'000'000);
+
2302 BEAST_EXPECT(ammAlice.expectBalances(
+
2303 XRP(9'000), USD(9'000), IOUAmount{9'000'000, 0}));
+
2304 });
+
2305
+
2306 // Equal withdrawal with a limit. Withdraw XRP200.
+
2307 // If proportional withdraw of USD is less than 100
+
2308 // then withdraw that amount, otherwise withdraw USD100
+
2309 // and proportionally withdraw XRP. It's the latter
+
2310 // in this case - XRP100/USD100.
+
2311 testAMM([&](AMM& ammAlice, Env&) {
+
2312 ammAlice.withdraw(alice, XRP(200), USD(100));
+
2313 BEAST_EXPECT(ammAlice.expectBalances(
+
2314 XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
+
2315 });
+
2316
+
2317 // Equal withdrawal with a limit. XRP100/USD100.
+
2318 testAMM([&](AMM& ammAlice, Env&) {
+
2319 ammAlice.withdraw(alice, XRP(100), USD(200));
+
2320 BEAST_EXPECT(ammAlice.expectBalances(
+
2321 XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
+
2322 });
+
2323
+
2324 // Single withdrawal by amount XRP1000
+
2325 testAMM(
+
2326 [&](AMM& ammAlice, Env& env) {
+
2327 ammAlice.withdraw(alice, XRP(1'000));
+
2328 if (!env.enabled(fixAMMv1_3))
+
2329 BEAST_EXPECT(ammAlice.expectBalances(
+
2330 XRP(9'000),
+
2331 USD(10'000),
+
2332 IOUAmount{9'486'832'98050514, -8}));
+
2333 else
+
2334 BEAST_EXPECT(ammAlice.expectBalances(
+
2335 XRPAmount{9'000'000'001},
+
2336 USD(10'000),
+
2337 IOUAmount{9'486'832'98050514, -8}));
+
2338 },
+
2339 std::nullopt,
+
2340 0,
+
2341 std::nullopt,
+
2342 {all, all - fixAMMv1_3});
+
2343
+
2344 // Single withdrawal by tokens 10000.
+
2345 testAMM([&](AMM& ammAlice, Env&) {
+
2346 ammAlice.withdraw(alice, 10'000, USD(0));
+
2347 BEAST_EXPECT(ammAlice.expectBalances(
+
2348 XRP(10'000), USD(9980.01), IOUAmount{9'990'000, 0}));
+
2349 });
+
2350
+
2351 // Withdraw all tokens.
+
2352 testAMM([&](AMM& ammAlice, Env& env) {
+
2353 env(trust(carol, STAmount{ammAlice.lptIssue(), 10'000}));
+
2354 // Can SetTrust only for AMM LP tokens
+
2355 env(trust(
+
2356 carol,
+
2357 STAmount{
+
2358 Issue{EUR.currency, ammAlice.ammAccount()}, 10'000}),
+
2359 ter(tecNO_PERMISSION));
+
2360 env.close();
+
2361 ammAlice.withdrawAll(alice);
+
2362 BEAST_EXPECT(!ammAlice.ammExists());
+
2363
+
2364 BEAST_EXPECT(!env.le(keylet::ownerDir(ammAlice.ammAccount())));
+
2365
+
2366 // Can create AMM for the XRP/USD pair
+
2367 AMM ammCarol(env, carol, XRP(10'000), USD(10'000));
+
2368 BEAST_EXPECT(ammCarol.expectBalances(
+
2369 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
+
2370 });
+
2371
+
2372 // Single deposit 1000USD, withdraw all tokens in USD
+
2373 testAMM([&](AMM& ammAlice, Env& env) {
+
2374 ammAlice.deposit(carol, USD(1'000));
+
2375 ammAlice.withdrawAll(carol, USD(0));
+
2376 BEAST_EXPECT(ammAlice.expectBalances(
+
2377 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
+
2378 BEAST_EXPECT(
+
2379 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
+
2380 });
+
2381
+
2382 // Single deposit 1000USD, withdraw all tokens in XRP
+
2383 testAMM([&](AMM& ammAlice, Env&) {
+
2384 ammAlice.deposit(carol, USD(1'000));
+
2385 ammAlice.withdrawAll(carol, XRP(0));
+
2386 BEAST_EXPECT(ammAlice.expectBalances(
+
2387 XRPAmount(9'090'909'091),
+
2388 STAmount{USD, UINT64_C(10'999'99999999999), -11},
+
2389 IOUAmount{10'000'000, 0}));
+
2390 });
+
2391
+
2392 // Single deposit/withdraw by the same account
+
2393 testAMM(
+
2394 [&](AMM& ammAlice, Env& env) {
+
2395 // Since a smaller amount might be deposited due to
+
2396 // the lp tokens adjustment, withdrawing by tokens
+
2397 // is generally preferred to withdrawing by amount.
+
2398 auto lpTokens = ammAlice.deposit(carol, USD(1'000));
+
2399 ammAlice.withdraw(carol, lpTokens, USD(0));
+
2400 lpTokens = ammAlice.deposit(carol, STAmount(USD, 1, -6));
+
2401 ammAlice.withdraw(carol, lpTokens, USD(0));
+
2402 lpTokens = ammAlice.deposit(carol, XRPAmount(1));
+
2403 ammAlice.withdraw(carol, lpTokens, XRPAmount(0));
+
2404 if (!env.enabled(fixAMMv1_3))
+
2405 BEAST_EXPECT(ammAlice.expectBalances(
+
2406 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
2407 else
+
2408 BEAST_EXPECT(ammAlice.expectBalances(
+
2409 XRPAmount(10'000'000'001),
+
2410 USD(10'000),
+
2411 ammAlice.tokens()));
+
2412 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
2413 },
+
2414 std::nullopt,
+
2415 0,
+
2416 std::nullopt,
+
2417 {all, all - fixAMMv1_3});
+
2418
+
2419 // Single deposit by different accounts and then withdraw
+
2420 // in reverse.
+
2421 testAMM([&](AMM& ammAlice, Env&) {
+
2422 auto const carolTokens = ammAlice.deposit(carol, USD(1'000));
+
2423 auto const aliceTokens = ammAlice.deposit(alice, USD(1'000));
+
2424 ammAlice.withdraw(alice, aliceTokens, USD(0));
+
2425 ammAlice.withdraw(carol, carolTokens, USD(0));
+
2426 BEAST_EXPECT(ammAlice.expectBalances(
+
2427 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
2428 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
2429 BEAST_EXPECT(ammAlice.expectLPTokens(alice, ammAlice.tokens()));
+
2430 });
+
2431
+
2432 // Equal deposit 10%, withdraw all tokens
+
2433 testAMM([&](AMM& ammAlice, Env&) {
+
2434 ammAlice.deposit(carol, 1'000'000);
+
2435 ammAlice.withdrawAll(carol);
+
2436 BEAST_EXPECT(ammAlice.expectBalances(
+
2437 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
+
2438 });
+
2439
+
2440 // Equal deposit 10%, withdraw all tokens in USD
+
2441 testAMM([&](AMM& ammAlice, Env&) {
+
2442 ammAlice.deposit(carol, 1'000'000);
+
2443 ammAlice.withdrawAll(carol, USD(0));
+
2444 BEAST_EXPECT(ammAlice.expectBalances(
+
2445 XRP(11'000),
+
2446 STAmount{USD, UINT64_C(9'090'909090909092), -12},
+
2447 IOUAmount{10'000'000, 0}));
2448 });
-
2449 // XRP
-
2450 testAMM([&](AMM& ammAlice, Env&) {
-
2451 ammAlice.withdraw(alice, XRPAmount{9'999'999'999});
-
2452 BEAST_EXPECT(ammAlice.expectBalances(
-
2453 XRPAmount{1}, USD(10'000), IOUAmount{100}));
-
2454 });
-
2455 }
-
2456
-
2457 void
-
2458 testInvalidFeeVote()
-
2459 {
-
2460 testcase("Invalid Fee Vote");
-
2461 using namespace jtx;
-
2462
-
2463 testAMM([&](AMM& ammAlice, Env& env) {
-
2464 // Invalid flags
-
2465 ammAlice.vote(
-
2466 std::nullopt,
-
2467 1'000,
-
2468 tfWithdrawAll,
-
2469 std::nullopt,
-
2470 std::nullopt,
-
2471 ter(temINVALID_FLAG));
-
2472
-
2473 // Invalid fee.
-
2474 ammAlice.vote(
-
2475 std::nullopt,
-
2476 1'001,
-
2477 std::nullopt,
-
2478 std::nullopt,
-
2479 std::nullopt,
-
2480 ter(temBAD_FEE));
-
2481 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
2482
-
2483 // Invalid Account
-
2484 Account bad("bad");
-
2485 env.memoize(bad);
-
2486 ammAlice.vote(
-
2487 bad,
-
2488 1'000,
-
2489 std::nullopt,
-
2490 seq(1),
-
2491 std::nullopt,
-
2492 ter(terNO_ACCOUNT));
-
2493
-
2494 // Invalid AMM
-
2495 ammAlice.vote(
-
2496 alice,
-
2497 1'000,
-
2498 std::nullopt,
-
2499 std::nullopt,
-
2500 {{USD, GBP}},
-
2501 ter(terNO_AMM));
-
2502
-
2503 // Account is not LP
-
2504 ammAlice.vote(
-
2505 carol,
-
2506 1'000,
-
2507 std::nullopt,
-
2508 std::nullopt,
-
2509 std::nullopt,
-
2510 ter(tecAMM_INVALID_TOKENS));
-
2511 });
-
2512
-
2513 // Invalid AMM
-
2514 testAMM([&](AMM& ammAlice, Env& env) {
-
2515 ammAlice.withdrawAll(alice);
-
2516 ammAlice.vote(
-
2517 alice,
-
2518 1'000,
-
2519 std::nullopt,
-
2520 std::nullopt,
-
2521 std::nullopt,
-
2522 ter(terNO_AMM));
-
2523 });
-
2524 }
-
2525
-
2526 void
-
2527 testFeeVote()
-
2528 {
-
2529 testcase("Fee Vote");
-
2530 using namespace jtx;
-
2531
-
2532 // One vote sets fee to 1%.
-
2533 testAMM([&](AMM& ammAlice, Env& env) {
-
2534 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{0}));
-
2535 ammAlice.vote({}, 1'000);
-
2536 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
-
2537 // Discounted fee is 1/10 of trading fee.
-
2538 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{0}));
-
2539 });
-
2540
-
2541 auto vote = [&](AMM& ammAlice,
-
2542 Env& env,
-
2543 int i,
-
2544 int fundUSD = 100'000,
-
2545 std::uint32_t tokens = 10'000'000,
-
2546 std::vector<Account>* accounts = nullptr) {
-
2547 Account a(std::to_string(i));
-
2548 fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
-
2549 ammAlice.deposit(a, tokens);
-
2550 ammAlice.vote(a, 50 * (i + 1));
-
2551 if (accounts)
-
2552 accounts->push_back(std::move(a));
-
2553 };
-
2554
-
2555 // Eight votes fill all voting slots, set fee 0.175%.
-
2556 testAMM([&](AMM& ammAlice, Env& env) {
-
2557 for (int i = 0; i < 7; ++i)
-
2558 vote(ammAlice, env, i, 10'000);
-
2559 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
2560 });
-
2561
-
2562 // Eight votes fill all voting slots, set fee 0.175%.
-
2563 // New vote, same account, sets fee 0.225%
-
2564 testAMM([&](AMM& ammAlice, Env& env) {
-
2565 for (int i = 0; i < 7; ++i)
-
2566 vote(ammAlice, env, i);
-
2567 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
2568 Account const a("0");
-
2569 ammAlice.vote(a, 450);
-
2570 BEAST_EXPECT(ammAlice.expectTradingFee(225));
-
2571 });
-
2572
-
2573 // Eight votes fill all voting slots, set fee 0.175%.
-
2574 // New vote, new account, higher vote weight, set higher fee 0.244%
-
2575 testAMM([&](AMM& ammAlice, Env& env) {
-
2576 for (int i = 0; i < 7; ++i)
-
2577 vote(ammAlice, env, i);
-
2578 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
2579 vote(ammAlice, env, 7, 100'000, 20'000'000);
-
2580 BEAST_EXPECT(ammAlice.expectTradingFee(244));
+
2449
+
2450 // Equal deposit 10%, withdraw all tokens in XRP
+
2451 testAMM([&](AMM& ammAlice, Env&) {
+
2452 ammAlice.deposit(carol, 1'000'000);
+
2453 ammAlice.withdrawAll(carol, XRP(0));
+
2454 BEAST_EXPECT(ammAlice.expectBalances(
+
2455 XRPAmount(9'090'909'091),
+
2456 USD(11'000),
+
2457 IOUAmount{10'000'000, 0}));
+
2458 });
+
2459
+
2460 // Withdraw with EPrice limit.
+
2461 testAMM(
+
2462 [&](AMM& ammAlice, Env& env) {
+
2463 ammAlice.deposit(carol, 1'000'000);
+
2464 ammAlice.withdraw(
+
2465 carol, USD(100), std::nullopt, IOUAmount{520, 0});
+
2466 BEAST_EXPECT(ammAlice.expectLPTokens(
+
2467 carol, IOUAmount{153'846'15384616, -8}));
+
2468 if (!env.enabled(fixAMMv1_1) && !env.enabled(fixAMMv1_3))
+
2469 BEAST_EXPECT(ammAlice.expectBalances(
+
2470 XRPAmount(11'000'000'000),
+
2471 STAmount{USD, UINT64_C(9'372'781065088757), -12},
+
2472 IOUAmount{10'153'846'15384616, -8}));
+
2473 else if (env.enabled(fixAMMv1_1) && !env.enabled(fixAMMv1_3))
+
2474 BEAST_EXPECT(ammAlice.expectBalances(
+
2475 XRPAmount(11'000'000'000),
+
2476 STAmount{USD, UINT64_C(9'372'781065088769), -12},
+
2477 IOUAmount{10'153'846'15384616, -8}));
+
2478 else if (env.enabled(fixAMMv1_3))
+
2479 BEAST_EXPECT(ammAlice.expectBalances(
+
2480 XRPAmount(11'000'000'000),
+
2481 STAmount{USD, UINT64_C(9'372'78106508877), -11},
+
2482 IOUAmount{10'153'846'15384616, -8}));
+
2483 ammAlice.withdrawAll(carol);
+
2484 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
2485 },
+
2486 {.features = {all, all - fixAMMv1_3, all - fixAMMv1_1 - fixAMMv1_3},
+
2487 .noLog = true});
+
2488
+
2489 // Withdraw with EPrice limit. AssetOut is 0.
+
2490 testAMM(
+
2491 [&](AMM& ammAlice, Env& env) {
+
2492 ammAlice.deposit(carol, 1'000'000);
+
2493 ammAlice.withdraw(
+
2494 carol, USD(0), std::nullopt, IOUAmount{520, 0});
+
2495 BEAST_EXPECT(ammAlice.expectLPTokens(
+
2496 carol, IOUAmount{153'846'15384616, -8}));
+
2497 if (!env.enabled(fixAMMv1_1) && !env.enabled(fixAMMv1_3))
+
2498 BEAST_EXPECT(ammAlice.expectBalances(
+
2499 XRP(11'000),
+
2500 STAmount{USD, UINT64_C(9'372'781065088757), -12},
+
2501 IOUAmount{10'153'846'15384616, -8}));
+
2502 else if (env.enabled(fixAMMv1_1) && !env.enabled(fixAMMv1_3))
+
2503 BEAST_EXPECT(ammAlice.expectBalances(
+
2504 XRP(11'000),
+
2505 STAmount{USD, UINT64_C(9'372'781065088769), -12},
+
2506 IOUAmount{10'153'846'15384616, -8}));
+
2507 else if (env.enabled(fixAMMv1_3))
+
2508 BEAST_EXPECT(ammAlice.expectBalances(
+
2509 XRP(11'000),
+
2510 STAmount{USD, UINT64_C(9'372'78106508877), -11},
+
2511 IOUAmount{10'153'846'15384616, -8}));
+
2512 },
+
2513 std::nullopt,
+
2514 0,
+
2515 std::nullopt,
+
2516 {all, all - fixAMMv1_3, all - fixAMMv1_1 - fixAMMv1_3});
+
2517
+
2518 // IOU to IOU + transfer fee
+
2519 {
+
2520 Env env{*this};
+
2521 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
+
2522 env(rate(gw, 1.25));
+
2523 env.close();
+
2524 // no transfer fee on create
+
2525 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
+
2526 BEAST_EXPECT(ammAlice.expectBalances(
+
2527 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
+
2528 BEAST_EXPECT(expectLine(env, alice, USD(0)));
+
2529 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
+
2530 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
+
2531 // no transfer fee on deposit
+
2532 ammAlice.deposit(carol, 10);
+
2533 BEAST_EXPECT(ammAlice.expectBalances(
+
2534 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
+
2535 BEAST_EXPECT(expectLine(env, carol, USD(0)));
+
2536 BEAST_EXPECT(expectLine(env, carol, BTC(0)));
+
2537 // no transfer fee on withdraw
+
2538 ammAlice.withdraw(carol, 10);
+
2539 BEAST_EXPECT(ammAlice.expectBalances(
+
2540 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
+
2541 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
+
2542 BEAST_EXPECT(expectLine(env, carol, USD(2'000)));
+
2543 BEAST_EXPECT(expectLine(env, carol, BTC(0.05)));
+
2544 }
+
2545
+
2546 // Tiny withdraw
+
2547 testAMM([&](AMM& ammAlice, Env&) {
+
2548 // By tokens
+
2549 ammAlice.withdraw(alice, IOUAmount{1, -3});
+
2550 BEAST_EXPECT(ammAlice.expectBalances(
+
2551 XRPAmount{9'999'999'999},
+
2552 STAmount{USD, UINT64_C(9'999'999999), -6},
+
2553 IOUAmount{9'999'999'999, -3}));
+
2554 });
+
2555 testAMM(
+
2556 [&](AMM& ammAlice, Env& env) {
+
2557 // Single XRP pool
+
2558 ammAlice.withdraw(alice, std::nullopt, XRPAmount{1});
+
2559 if (!env.enabled(fixAMMv1_3))
+
2560 BEAST_EXPECT(ammAlice.expectBalances(
+
2561 XRPAmount{9'999'999'999},
+
2562 USD(10'000),
+
2563 IOUAmount{9'999'999'9995, -4}));
+
2564 else
+
2565 BEAST_EXPECT(ammAlice.expectBalances(
+
2566 XRP(10'000),
+
2567 USD(10'000),
+
2568 IOUAmount{9'999'999'9995, -4}));
+
2569 },
+
2570 std::nullopt,
+
2571 0,
+
2572 std::nullopt,
+
2573 {all, all - fixAMMv1_3});
+
2574 testAMM([&](AMM& ammAlice, Env&) {
+
2575 // Single USD pool
+
2576 ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
+
2577 BEAST_EXPECT(ammAlice.expectBalances(
+
2578 XRP(10'000),
+
2579 STAmount{USD, UINT64_C(9'999'9999999999), -10},
+
2580 IOUAmount{9'999'999'99999995, -8}));
2581 });
2582
-
2583 // Eight votes fill all voting slots, set fee 0.219%.
-
2584 // New vote, new account, higher vote weight, set smaller fee 0.206%
-
2585 testAMM([&](AMM& ammAlice, Env& env) {
-
2586 for (int i = 7; i > 0; --i)
-
2587 vote(ammAlice, env, i);
-
2588 BEAST_EXPECT(ammAlice.expectTradingFee(219));
-
2589 vote(ammAlice, env, 0, 100'000, 20'000'000);
-
2590 BEAST_EXPECT(ammAlice.expectTradingFee(206));
-
2591 });
-
2592
-
2593 // Eight votes fill all voting slots. The accounts then withdraw all
-
2594 // tokens. An account sets a new fee and the previous slots are
-
2595 // deleted.
-
2596 testAMM([&](AMM& ammAlice, Env& env) {
-
2597 std::vector<Account> accounts;
-
2598 for (int i = 0; i < 7; ++i)
-
2599 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
-
2600 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
2601 for (int i = 0; i < 7; ++i)
-
2602 ammAlice.withdrawAll(accounts[i]);
-
2603 ammAlice.deposit(carol, 10'000'000);
-
2604 ammAlice.vote(carol, 1'000);
-
2605 // The initial LP set the fee to 1000. Carol gets 50% voting
-
2606 // power, and the new fee is 500.
-
2607 BEAST_EXPECT(ammAlice.expectTradingFee(500));
+
2583 // Withdraw close to entire pool
+
2584 // Equal by tokens
+
2585 testAMM([&](AMM& ammAlice, Env&) {
+
2586 ammAlice.withdraw(alice, IOUAmount{9'999'999'999, -3});
+
2587 BEAST_EXPECT(ammAlice.expectBalances(
+
2588 XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
+
2589 });
+
2590 // USD by tokens
+
2591 testAMM([&](AMM& ammAlice, Env&) {
+
2592 ammAlice.withdraw(alice, IOUAmount{9'999'999}, USD(0));
+
2593 BEAST_EXPECT(ammAlice.expectBalances(
+
2594 XRP(10'000), STAmount{USD, 1, -10}, IOUAmount{1}));
+
2595 });
+
2596 // XRP by tokens
+
2597 testAMM([&](AMM& ammAlice, Env&) {
+
2598 ammAlice.withdraw(alice, IOUAmount{9'999'900}, XRP(0));
+
2599 BEAST_EXPECT(ammAlice.expectBalances(
+
2600 XRPAmount{1}, USD(10'000), IOUAmount{100}));
+
2601 });
+
2602 // USD
+
2603 testAMM([&](AMM& ammAlice, Env&) {
+
2604 ammAlice.withdraw(
+
2605 alice, STAmount{USD, UINT64_C(9'999'99999999999), -11});
+
2606 BEAST_EXPECT(ammAlice.expectBalances(
+
2607 XRP(10000), STAmount{USD, 1, -11}, IOUAmount{316227765, -9}));
2608 });
-
2609
-
2610 // Eight votes fill all voting slots. The accounts then withdraw some
-
2611 // tokens. The new vote doesn't get the voting power but
-
2612 // the slots are refreshed and the fee is updated.
-
2613 testAMM([&](AMM& ammAlice, Env& env) {
-
2614 std::vector<Account> accounts;
-
2615 for (int i = 0; i < 7; ++i)
-
2616 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
-
2617 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
2618 for (int i = 0; i < 7; ++i)
-
2619 ammAlice.withdraw(accounts[i], 9'000'000);
-
2620 ammAlice.deposit(carol, 1'000);
-
2621 // The vote is not added to the slots
-
2622 ammAlice.vote(carol, 1'000);
-
2623 auto const info = ammAlice.ammRpcInfo()[jss::amm][jss::vote_slots];
-
2624 for (std::uint16_t i = 0; i < info.size(); ++i)
-
2625 BEAST_EXPECT(info[i][jss::account] != carol.human());
-
2626 // But the slots are refreshed and the fee is changed
-
2627 BEAST_EXPECT(ammAlice.expectTradingFee(82));
-
2628 });
-
2629 }
-
2630
-
2631 void
-
2632 testInvalidBid()
-
2633 {
-
2634 testcase("Invalid Bid");
-
2635 using namespace jtx;
-
2636 using namespace std::chrono;
-
2637
-
2638 // burn all the LPTokens through a AMMBid transaction
-
2639 {
-
2640 Env env(*this);
-
2641 fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
-
2642 AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
-
2643
-
2644 // auction slot is owned by the creator of the AMM i.e. gw
-
2645 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
-
2646
-
2647 // gw attempts to burn all her LPTokens through a bid transaction
-
2648 // this transaction fails because AMMBid transaction can not burn
-
2649 // all the outstanding LPTokens
-
2650 env(amm.bid({
-
2651 .account = gw,
-
2652 .bidMin = 1'000'000,
-
2653 }),
-
2654 ter(tecAMM_INVALID_TOKENS));
-
2655 }
-
2656
-
2657 // burn all the LPTokens through a AMMBid transaction
-
2658 {
-
2659 Env env(*this);
-
2660 fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
-
2661 AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
+
2609 // XRP
+
2610 testAMM([&](AMM& ammAlice, Env&) {
+
2611 ammAlice.withdraw(alice, XRPAmount{9'999'999'999});
+
2612 BEAST_EXPECT(ammAlice.expectBalances(
+
2613 XRPAmount{1}, USD(10'000), IOUAmount{100}));
+
2614 });
+
2615 }
+
2616
+
2617 void
+
2618 testInvalidFeeVote()
+
2619 {
+
2620 testcase("Invalid Fee Vote");
+
2621 using namespace jtx;
+
2622
+
2623 testAMM([&](AMM& ammAlice, Env& env) {
+
2624 // Invalid flags
+
2625 ammAlice.vote(
+
2626 std::nullopt,
+
2627 1'000,
+
2628 tfWithdrawAll,
+
2629 std::nullopt,
+
2630 std::nullopt,
+
2631 ter(temINVALID_FLAG));
+
2632
+
2633 // Invalid fee.
+
2634 ammAlice.vote(
+
2635 std::nullopt,
+
2636 1'001,
+
2637 std::nullopt,
+
2638 std::nullopt,
+
2639 std::nullopt,
+
2640 ter(temBAD_FEE));
+
2641 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
2642
+
2643 // Invalid Account
+
2644 Account bad("bad");
+
2645 env.memoize(bad);
+
2646 ammAlice.vote(
+
2647 bad,
+
2648 1'000,
+
2649 std::nullopt,
+
2650 seq(1),
+
2651 std::nullopt,
+
2652 ter(terNO_ACCOUNT));
+
2653
+
2654 // Invalid AMM
+
2655 ammAlice.vote(
+
2656 alice,
+
2657 1'000,
+
2658 std::nullopt,
+
2659 std::nullopt,
+
2660 {{USD, GBP}},
+
2661 ter(terNO_AMM));
2662
-
2663 // auction slot is owned by the creator of the AMM i.e. gw
-
2664 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
-
2665
-
2666 // gw burns all but one of its LPTokens through a bid transaction
-
2667 // this transaction suceeds because the bid price is less than
-
2668 // the total outstanding LPToken balance
-
2669 env(amm.bid({
-
2670 .account = gw,
-
2671 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
-
2672 }),
-
2673 ter(tesSUCCESS))
-
2674 .close();
-
2675
-
2676 // gw must own the auction slot
-
2677 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{999'999}));
-
2678
-
2679 // 999'999 tokens are burned, only 1 LPToken is owned by gw
-
2680 BEAST_EXPECT(
-
2681 amm.expectBalances(XRP(1'000), USD(1'000), IOUAmount{1}));
-
2682
-
2683 // gw owns only 1 LPToken in its balance
-
2684 BEAST_EXPECT(Number{amm.getLPTokensBalance(gw)} == 1);
+
2663 // Account is not LP
+
2664 ammAlice.vote(
+
2665 carol,
+
2666 1'000,
+
2667 std::nullopt,
+
2668 std::nullopt,
+
2669 std::nullopt,
+
2670 ter(tecAMM_INVALID_TOKENS));
+
2671 });
+
2672
+
2673 // Invalid AMM
+
2674 testAMM([&](AMM& ammAlice, Env& env) {
+
2675 ammAlice.withdrawAll(alice);
+
2676 ammAlice.vote(
+
2677 alice,
+
2678 1'000,
+
2679 std::nullopt,
+
2680 std::nullopt,
+
2681 std::nullopt,
+
2682 ter(terNO_AMM));
+
2683 });
+
2684 }
2685
-
2686 // gw attempts to burn the last of its LPTokens in an AMMBid
-
2687 // transaction. This transaction fails because it would burn all
-
2688 // the remaining LPTokens
-
2689 env(amm.bid({
-
2690 .account = gw,
-
2691 .bidMin = 1,
-
2692 }),
-
2693 ter(tecAMM_INVALID_TOKENS));
-
2694 }
-
2695
-
2696 testAMM([&](AMM& ammAlice, Env& env) {
-
2697 // Invalid flags
-
2698 env(ammAlice.bid({
-
2699 .account = carol,
-
2700 .bidMin = 0,
-
2701 .flags = tfWithdrawAll,
-
2702 }),
-
2703 ter(temINVALID_FLAG));
-
2704
-
2705 ammAlice.deposit(carol, 1'000'000);
-
2706 // Invalid Bid price <= 0
-
2707 for (auto bid : {0, -100})
-
2708 {
-
2709 env(ammAlice.bid({
-
2710 .account = carol,
-
2711 .bidMin = bid,
-
2712 }),
-
2713 ter(temBAD_AMOUNT));
-
2714 env(ammAlice.bid({
-
2715 .account = carol,
-
2716 .bidMax = bid,
-
2717 }),
-
2718 ter(temBAD_AMOUNT));
-
2719 }
-
2720
-
2721 // Invlaid Min/Max combination
-
2722 env(ammAlice.bid({
-
2723 .account = carol,
-
2724 .bidMin = 200,
-
2725 .bidMax = 100,
-
2726 }),
-
2727 ter(tecAMM_INVALID_TOKENS));
-
2728
-
2729 // Invalid Account
-
2730 Account bad("bad");
-
2731 env.memoize(bad);
-
2732 env(ammAlice.bid({
-
2733 .account = bad,
-
2734 .bidMax = 100,
-
2735 }),
-
2736 seq(1),
-
2737 ter(terNO_ACCOUNT));
-
2738
-
2739 // Account is not LP
-
2740 Account const dan("dan");
-
2741 env.fund(XRP(1'000), dan);
-
2742 env(ammAlice.bid({
-
2743 .account = dan,
-
2744 .bidMin = 100,
-
2745 }),
-
2746 ter(tecAMM_INVALID_TOKENS));
-
2747 env(ammAlice.bid({
-
2748 .account = dan,
-
2749 }),
-
2750 ter(tecAMM_INVALID_TOKENS));
-
2751
-
2752 // Auth account is invalid.
-
2753 env(ammAlice.bid({
-
2754 .account = carol,
-
2755 .bidMin = 100,
-
2756 .authAccounts = {bob},
-
2757 }),
-
2758 ter(terNO_ACCOUNT));
-
2759
-
2760 // Invalid Assets
-
2761 env(ammAlice.bid({
-
2762 .account = alice,
-
2763 .bidMax = 100,
-
2764 .assets = {{USD, GBP}},
-
2765 }),
-
2766 ter(terNO_AMM));
-
2767
-
2768 // Invalid Min/Max issue
-
2769 env(ammAlice.bid({
-
2770 .account = alice,
-
2771 .bidMax = STAmount{USD, 100},
-
2772 }),
-
2773 ter(temBAD_AMM_TOKENS));
-
2774 env(ammAlice.bid({
-
2775 .account = alice,
-
2776 .bidMin = STAmount{USD, 100},
-
2777 }),
-
2778 ter(temBAD_AMM_TOKENS));
-
2779 });
-
2780
-
2781 // Invalid AMM
-
2782 testAMM([&](AMM& ammAlice, Env& env) {
-
2783 ammAlice.withdrawAll(alice);
-
2784 env(ammAlice.bid({
-
2785 .account = alice,
-
2786 .bidMax = 100,
-
2787 }),
-
2788 ter(terNO_AMM));
-
2789 });
-
2790
-
2791 // More than four Auth accounts.
-
2792 testAMM([&](AMM& ammAlice, Env& env) {
-
2793 Account ed("ed");
-
2794 Account bill("bill");
-
2795 Account scott("scott");
-
2796 Account james("james");
-
2797 env.fund(XRP(1'000), bob, ed, bill, scott, james);
-
2798 env.close();
-
2799 ammAlice.deposit(carol, 1'000'000);
-
2800 env(ammAlice.bid({
-
2801 .account = carol,
-
2802 .bidMin = 100,
-
2803 .authAccounts = {bob, ed, bill, scott, james},
-
2804 }),
-
2805 ter(temMALFORMED));
-
2806 });
-
2807
-
2808 // Bid price exceeds LP owned tokens
-
2809 testAMM([&](AMM& ammAlice, Env& env) {
-
2810 fund(env, gw, {bob}, XRP(1'000), {USD(100)}, Fund::Acct);
-
2811 ammAlice.deposit(carol, 1'000'000);
-
2812 ammAlice.deposit(bob, 10);
-
2813 env(ammAlice.bid({
-
2814 .account = carol,
-
2815 .bidMin = 1'000'001,
-
2816 }),
-
2817 ter(tecAMM_INVALID_TOKENS));
-
2818 env(ammAlice.bid({
-
2819 .account = carol,
-
2820 .bidMax = 1'000'001,
-
2821 }),
-
2822 ter(tecAMM_INVALID_TOKENS));
-
2823 env(ammAlice.bid({
-
2824 .account = carol,
-
2825 .bidMin = 1'000,
-
2826 }));
-
2827 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
-
2828 // Slot purchase price is more than 1000 but bob only has 10 tokens
-
2829 env(ammAlice.bid({
-
2830 .account = bob,
-
2831 }),
-
2832 ter(tecAMM_INVALID_TOKENS));
-
2833 });
+
2686 void
+
2687 testFeeVote()
+
2688 {
+
2689 testcase("Fee Vote");
+
2690 using namespace jtx;
+
2691 auto const all = supported_amendments();
+
2692
+
2693 // One vote sets fee to 1%.
+
2694 testAMM([&](AMM& ammAlice, Env& env) {
+
2695 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{0}));
+
2696 ammAlice.vote({}, 1'000);
+
2697 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
+
2698 // Discounted fee is 1/10 of trading fee.
+
2699 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{0}));
+
2700 });
+
2701
+
2702 auto vote = [&](AMM& ammAlice,
+
2703 Env& env,
+
2704 int i,
+
2705 int fundUSD = 100'000,
+
2706 std::uint32_t tokens = 10'000'000,
+
2707 std::vector<Account>* accounts = nullptr) {
+
2708 Account a(std::to_string(i));
+
2709 // post-amendment the amount to deposit is slightly higher
+
2710 // in order to ensure AMM invariant sqrt(asset1 * asset2) >= tokens
+
2711 // fund just one USD higher in this case, which is enough for
+
2712 // deposit to succeed
+
2713 if (env.enabled(fixAMMv1_3))
+
2714 ++fundUSD;
+
2715 fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
+
2716 ammAlice.deposit(a, tokens);
+
2717 ammAlice.vote(a, 50 * (i + 1));
+
2718 if (accounts)
+
2719 accounts->push_back(std::move(a));
+
2720 };
+
2721
+
2722 // Eight votes fill all voting slots, set fee 0.175%.
+
2723 testAMM(
+
2724 [&](AMM& ammAlice, Env& env) {
+
2725 for (int i = 0; i < 7; ++i)
+
2726 vote(ammAlice, env, i, 10'000);
+
2727 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2728 },
+
2729 std::nullopt,
+
2730 0,
+
2731 std::nullopt,
+
2732 {all});
+
2733
+
2734 // Eight votes fill all voting slots, set fee 0.175%.
+
2735 // New vote, same account, sets fee 0.225%
+
2736 testAMM([&](AMM& ammAlice, Env& env) {
+
2737 for (int i = 0; i < 7; ++i)
+
2738 vote(ammAlice, env, i);
+
2739 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2740 Account const a("0");
+
2741 ammAlice.vote(a, 450);
+
2742 BEAST_EXPECT(ammAlice.expectTradingFee(225));
+
2743 });
+
2744
+
2745 // Eight votes fill all voting slots, set fee 0.175%.
+
2746 // New vote, new account, higher vote weight, set higher fee 0.244%
+
2747 testAMM([&](AMM& ammAlice, Env& env) {
+
2748 for (int i = 0; i < 7; ++i)
+
2749 vote(ammAlice, env, i);
+
2750 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2751 vote(ammAlice, env, 7, 100'000, 20'000'000);
+
2752 BEAST_EXPECT(ammAlice.expectTradingFee(244));
+
2753 });
+
2754
+
2755 // Eight votes fill all voting slots, set fee 0.219%.
+
2756 // New vote, new account, higher vote weight, set smaller fee 0.206%
+
2757 testAMM([&](AMM& ammAlice, Env& env) {
+
2758 for (int i = 7; i > 0; --i)
+
2759 vote(ammAlice, env, i);
+
2760 BEAST_EXPECT(ammAlice.expectTradingFee(219));
+
2761 vote(ammAlice, env, 0, 100'000, 20'000'000);
+
2762 BEAST_EXPECT(ammAlice.expectTradingFee(206));
+
2763 });
+
2764
+
2765 // Eight votes fill all voting slots. The accounts then withdraw all
+
2766 // tokens. An account sets a new fee and the previous slots are
+
2767 // deleted.
+
2768 testAMM([&](AMM& ammAlice, Env& env) {
+
2769 std::vector<Account> accounts;
+
2770 for (int i = 0; i < 7; ++i)
+
2771 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
+
2772 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2773 for (int i = 0; i < 7; ++i)
+
2774 ammAlice.withdrawAll(accounts[i]);
+
2775 ammAlice.deposit(carol, 10'000'000);
+
2776 ammAlice.vote(carol, 1'000);
+
2777 // The initial LP set the fee to 1000. Carol gets 50% voting
+
2778 // power, and the new fee is 500.
+
2779 BEAST_EXPECT(ammAlice.expectTradingFee(500));
+
2780 });
+
2781
+
2782 // Eight votes fill all voting slots. The accounts then withdraw some
+
2783 // tokens. The new vote doesn't get the voting power but
+
2784 // the slots are refreshed and the fee is updated.
+
2785 testAMM([&](AMM& ammAlice, Env& env) {
+
2786 std::vector<Account> accounts;
+
2787 for (int i = 0; i < 7; ++i)
+
2788 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
+
2789 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2790 for (int i = 0; i < 7; ++i)
+
2791 ammAlice.withdraw(accounts[i], 9'000'000);
+
2792 ammAlice.deposit(carol, 1'000);
+
2793 // The vote is not added to the slots
+
2794 ammAlice.vote(carol, 1'000);
+
2795 auto const info = ammAlice.ammRpcInfo()[jss::amm][jss::vote_slots];
+
2796 for (std::uint16_t i = 0; i < info.size(); ++i)
+
2797 BEAST_EXPECT(info[i][jss::account] != carol.human());
+
2798 // But the slots are refreshed and the fee is changed
+
2799 BEAST_EXPECT(ammAlice.expectTradingFee(82));
+
2800 });
+
2801 }
+
2802
+
2803 void
+
2804 testInvalidBid()
+
2805 {
+
2806 testcase("Invalid Bid");
+
2807 using namespace jtx;
+
2808 using namespace std::chrono;
+
2809
+
2810 // burn all the LPTokens through a AMMBid transaction
+
2811 {
+
2812 Env env(*this);
+
2813 fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
+
2814 AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
+
2815
+
2816 // auction slot is owned by the creator of the AMM i.e. gw
+
2817 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
+
2818
+
2819 // gw attempts to burn all her LPTokens through a bid transaction
+
2820 // this transaction fails because AMMBid transaction can not burn
+
2821 // all the outstanding LPTokens
+
2822 env(amm.bid({
+
2823 .account = gw,
+
2824 .bidMin = 1'000'000,
+
2825 }),
+
2826 ter(tecAMM_INVALID_TOKENS));
+
2827 }
+
2828
+
2829 // burn all the LPTokens through a AMMBid transaction
+
2830 {
+
2831 Env env(*this);
+
2832 fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
+
2833 AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
2834
-
2835 // Bid all tokens, still own the slot
-
2836 {
-
2837 Env env(*this);
-
2838 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'000)});
-
2839 AMM amm(env, gw, XRP(10), USD(1'000));
-
2840 auto const lpIssue = amm.lptIssue();
-
2841 env.trust(STAmount{lpIssue, 100}, alice);
-
2842 env.trust(STAmount{lpIssue, 50}, bob);
-
2843 env(pay(gw, alice, STAmount{lpIssue, 100}));
-
2844 env(pay(gw, bob, STAmount{lpIssue, 50}));
-
2845 env(amm.bid({.account = alice, .bidMin = 100}));
-
2846 // Alice doesn't have any more tokens, but
-
2847 // she still owns the slot.
-
2848 env(amm.bid({
-
2849 .account = bob,
-
2850 .bidMax = 50,
-
2851 }),
-
2852 ter(tecAMM_FAILED));
-
2853 }
-
2854 }
-
2855
-
2856 void
-
2857 testBid(FeatureBitset features)
-
2858 {
-
2859 testcase("Bid");
-
2860 using namespace jtx;
-
2861 using namespace std::chrono;
-
2862
-
2863 // Auction slot initially is owned by AMM creator, who pays 0 price.
-
2864
-
2865 // Bid 110 tokens. Pay bidMin.
-
2866 testAMM(
-
2867 [&](AMM& ammAlice, Env& env) {
-
2868 ammAlice.deposit(carol, 1'000'000);
-
2869 env(ammAlice.bid({.account = carol, .bidMin = 110}));
-
2870 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
-
2871 // 110 tokens are burned.
-
2872 BEAST_EXPECT(ammAlice.expectBalances(
-
2873 XRP(11'000), USD(11'000), IOUAmount{10'999'890, 0}));
-
2874 },
-
2875 std::nullopt,
-
2876 0,
-
2877 std::nullopt,
-
2878 {features});
-
2879
-
2880 // Bid with min/max when the pay price is less than min.
-
2881 testAMM(
-
2882 [&](AMM& ammAlice, Env& env) {
-
2883 ammAlice.deposit(carol, 1'000'000);
-
2884 // Bid exactly 110. Pay 110 because the pay price is < 110.
-
2885 env(ammAlice.bid(
-
2886 {.account = carol, .bidMin = 110, .bidMax = 110}));
-
2887 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
-
2888 BEAST_EXPECT(ammAlice.expectBalances(
-
2889 XRP(11'000), USD(11'000), IOUAmount{10'999'890}));
-
2890 // Bid exactly 180-200. Pay 180 because the pay price is < 180.
-
2891 env(ammAlice.bid(
-
2892 {.account = alice, .bidMin = 180, .bidMax = 200}));
-
2893 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{180}));
-
2894 BEAST_EXPECT(ammAlice.expectBalances(
-
2895 XRP(11'000), USD(11'000), IOUAmount{10'999'814'5, -1}));
-
2896 },
-
2897 std::nullopt,
-
2898 0,
-
2899 std::nullopt,
-
2900 {features});
-
2901
-
2902 // Start bid at bidMin 110.
-
2903 testAMM(
-
2904 [&](AMM& ammAlice, Env& env) {
-
2905 ammAlice.deposit(carol, 1'000'000);
-
2906 // Bid, pay bidMin.
-
2907 env(ammAlice.bid({.account = carol, .bidMin = 110}));
-
2908 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
-
2909
-
2910 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
-
2911 ammAlice.deposit(bob, 1'000'000);
-
2912 // Bid, pay the computed price.
-
2913 env(ammAlice.bid({.account = bob}));
-
2914 BEAST_EXPECT(
-
2915 ammAlice.expectAuctionSlot(0, 0, IOUAmount(1155, -1)));
-
2916
-
2917 // Bid bidMax fails because the computed price is higher.
-
2918 env(ammAlice.bid({
-
2919 .account = carol,
-
2920 .bidMax = 120,
-
2921 }),
-
2922 ter(tecAMM_FAILED));
-
2923 // Bid MaxSlotPrice succeeds - pay computed price
-
2924 env(ammAlice.bid({.account = carol, .bidMax = 600}));
-
2925 BEAST_EXPECT(
-
2926 ammAlice.expectAuctionSlot(0, 0, IOUAmount{121'275, -3}));
-
2927
-
2928 // Bid Min/MaxSlotPrice fails because the computed price is not
-
2929 // in range
-
2930 env(ammAlice.bid({
-
2931 .account = carol,
-
2932 .bidMin = 10,
-
2933 .bidMax = 100,
-
2934 }),
-
2935 ter(tecAMM_FAILED));
-
2936 // Bid Min/MaxSlotPrice succeeds - pay computed price
-
2937 env(ammAlice.bid(
-
2938 {.account = carol, .bidMin = 100, .bidMax = 600}));
-
2939 BEAST_EXPECT(
-
2940 ammAlice.expectAuctionSlot(0, 0, IOUAmount{127'33875, -5}));
-
2941 },
-
2942 std::nullopt,
-
2943 0,
-
2944 std::nullopt,
-
2945 {features});
-
2946
-
2947 // Slot states.
-
2948 testAMM(
-
2949 [&](AMM& ammAlice, Env& env) {
-
2950 ammAlice.deposit(carol, 1'000'000);
-
2951
-
2952 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
-
2953 ammAlice.deposit(bob, 1'000'000);
-
2954 BEAST_EXPECT(ammAlice.expectBalances(
-
2955 XRP(12'000), USD(12'000), IOUAmount{12'000'000, 0}));
-
2956
-
2957 // Initial state. Pay bidMin.
-
2958 env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
-
2959 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
-
2960
-
2961 // 1st Interval after close, price for 0th interval.
-
2962 env(ammAlice.bid({.account = bob}));
-
2963 env.close(seconds(AUCTION_SLOT_INTERVAL_DURATION + 1));
-
2964 BEAST_EXPECT(
-
2965 ammAlice.expectAuctionSlot(0, 1, IOUAmount{1'155, -1}));
-
2966
-
2967 // 10th Interval after close, price for 1st interval.
-
2968 env(ammAlice.bid({.account = carol}));
-
2969 env.close(seconds(10 * AUCTION_SLOT_INTERVAL_DURATION + 1));
-
2970 BEAST_EXPECT(
-
2971 ammAlice.expectAuctionSlot(0, 10, IOUAmount{121'275, -3}));
-
2972
-
2973 // 20th Interval (expired) after close, price for 10th interval.
-
2974 env(ammAlice.bid({.account = bob}));
-
2975 env.close(seconds(
-
2976 AUCTION_SLOT_TIME_INTERVALS *
-
2977 AUCTION_SLOT_INTERVAL_DURATION +
-
2978 1));
-
2979 BEAST_EXPECT(ammAlice.expectAuctionSlot(
-
2980 0, std::nullopt, IOUAmount{127'33875, -5}));
-
2981
-
2982 // 0 Interval.
-
2983 env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
-
2984 BEAST_EXPECT(ammAlice.expectAuctionSlot(
-
2985 0, std::nullopt, IOUAmount{110}));
-
2986 // ~321.09 tokens burnt on bidding fees.
-
2987 BEAST_EXPECT(ammAlice.expectBalances(
-
2988 XRP(12'000), USD(12'000), IOUAmount{11'999'678'91, -2}));
-
2989 },
-
2990 std::nullopt,
-
2991 0,
-
2992 std::nullopt,
-
2993 {features});
-
2994
-
2995 // Pool's fee 1%. Bid bidMin.
-
2996 // Auction slot owner and auth account trade at discounted fee -
-
2997 // 1/10 of the trading fee.
-
2998 // Other accounts trade at 1% fee.
-
2999 testAMM(
-
3000 [&](AMM& ammAlice, Env& env) {
-
3001 Account const dan("dan");
-
3002 Account const ed("ed");
-
3003 fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::Acct);
-
3004 ammAlice.deposit(bob, 1'000'000);
-
3005 ammAlice.deposit(ed, 1'000'000);
-
3006 ammAlice.deposit(carol, 500'000);
-
3007 ammAlice.deposit(dan, 500'000);
-
3008 auto ammTokens = ammAlice.getLPTokensBalance();
-
3009 env(ammAlice.bid({
-
3010 .account = carol,
-
3011 .bidMin = 120,
-
3012 .authAccounts = {bob, ed},
-
3013 }));
-
3014 auto const slotPrice = IOUAmount{5'200};
-
3015 ammTokens -= slotPrice;
-
3016 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, slotPrice));
-
3017 BEAST_EXPECT(ammAlice.expectBalances(
-
3018 XRP(13'000), USD(13'000), ammTokens));
-
3019 // Discounted trade
-
3020 for (int i = 0; i < 10; ++i)
-
3021 {
-
3022 auto tokens = ammAlice.deposit(carol, USD(100));
-
3023 ammAlice.withdraw(carol, tokens, USD(0));
-
3024 tokens = ammAlice.deposit(bob, USD(100));
-
3025 ammAlice.withdraw(bob, tokens, USD(0));
-
3026 tokens = ammAlice.deposit(ed, USD(100));
-
3027 ammAlice.withdraw(ed, tokens, USD(0));
-
3028 }
-
3029 // carol, bob, and ed pay ~0.99USD in fees.
-
3030 if (!features[fixAMMv1_1])
-
3031 {
-
3032 BEAST_EXPECT(
-
3033 env.balance(carol, USD) ==
-
3034 STAmount(USD, UINT64_C(29'499'00572620545), -11));
-
3035 BEAST_EXPECT(
-
3036 env.balance(bob, USD) ==
-
3037 STAmount(USD, UINT64_C(18'999'00572616195), -11));
-
3038 BEAST_EXPECT(
-
3039 env.balance(ed, USD) ==
-
3040 STAmount(USD, UINT64_C(18'999'00572611841), -11));
-
3041 // USD pool is slightly higher because of the fees.
-
3042 BEAST_EXPECT(ammAlice.expectBalances(
-
3043 XRP(13'000),
-
3044 STAmount(USD, UINT64_C(13'002'98282151419), -11),
-
3045 ammTokens));
-
3046 }
-
3047 else
-
3048 {
-
3049 BEAST_EXPECT(
-
3050 env.balance(carol, USD) ==
-
3051 STAmount(USD, UINT64_C(29'499'00572620544), -11));
-
3052 BEAST_EXPECT(
-
3053 env.balance(bob, USD) ==
-
3054 STAmount(USD, UINT64_C(18'999'00572616194), -11));
-
3055 BEAST_EXPECT(
-
3056 env.balance(ed, USD) ==
-
3057 STAmount(USD, UINT64_C(18'999'0057261184), -10));
-
3058 // USD pool is slightly higher because of the fees.
-
3059 BEAST_EXPECT(ammAlice.expectBalances(
-
3060 XRP(13'000),
-
3061 STAmount(USD, UINT64_C(13'002'98282151422), -11),
-
3062 ammTokens));
-
3063 }
-
3064 ammTokens = ammAlice.getLPTokensBalance();
-
3065 // Trade with the fee
-
3066 for (int i = 0; i < 10; ++i)
-
3067 {
-
3068 auto const tokens = ammAlice.deposit(dan, USD(100));
-
3069 ammAlice.withdraw(dan, tokens, USD(0));
-
3070 }
-
3071 // dan pays ~9.94USD, which is ~10 times more in fees than
-
3072 // carol, bob, ed. the discounted fee is 10 times less
-
3073 // than the trading fee.
-
3074 if (!features[fixAMMv1_1])
-
3075 {
-
3076 BEAST_EXPECT(
-
3077 env.balance(dan, USD) ==
-
3078 STAmount(USD, UINT64_C(19'490'056722744), -9));
-
3079 // USD pool gains more in dan's fees.
-
3080 BEAST_EXPECT(ammAlice.expectBalances(
-
3081 XRP(13'000),
-
3082 STAmount{USD, UINT64_C(13'012'92609877019), -11},
-
3083 ammTokens));
-
3084 // Discounted fee payment
-
3085 ammAlice.deposit(carol, USD(100));
-
3086 ammTokens = ammAlice.getLPTokensBalance();
-
3087 BEAST_EXPECT(ammAlice.expectBalances(
-
3088 XRP(13'000),
-
3089 STAmount{USD, UINT64_C(13'112'92609877019), -11},
-
3090 ammTokens));
-
3091 env(pay(carol, bob, USD(100)),
-
3092 path(~USD),
-
3093 sendmax(XRP(110)));
-
3094 env.close();
-
3095 // carol pays 100000 drops in fees
-
3096 // 99900668XRP swapped in for 100USD
-
3097 BEAST_EXPECT(ammAlice.expectBalances(
-
3098 XRPAmount{13'100'000'668},
-
3099 STAmount{USD, UINT64_C(13'012'92609877019), -11},
-
3100 ammTokens));
-
3101 }
-
3102 else
-
3103 {
-
3104 BEAST_EXPECT(
-
3105 env.balance(dan, USD) ==
-
3106 STAmount(USD, UINT64_C(19'490'05672274399), -11));
-
3107 // USD pool gains more in dan's fees.
-
3108 BEAST_EXPECT(ammAlice.expectBalances(
-
3109 XRP(13'000),
-
3110 STAmount{USD, UINT64_C(13'012'92609877023), -11},
-
3111 ammTokens));
-
3112 // Discounted fee payment
-
3113 ammAlice.deposit(carol, USD(100));
-
3114 ammTokens = ammAlice.getLPTokensBalance();
-
3115 BEAST_EXPECT(ammAlice.expectBalances(
-
3116 XRP(13'000),
-
3117 STAmount{USD, UINT64_C(13'112'92609877023), -11},
-
3118 ammTokens));
-
3119 env(pay(carol, bob, USD(100)),
-
3120 path(~USD),
-
3121 sendmax(XRP(110)));
-
3122 env.close();
-
3123 // carol pays 100000 drops in fees
-
3124 // 99900668XRP swapped in for 100USD
-
3125 BEAST_EXPECT(ammAlice.expectBalances(
-
3126 XRPAmount{13'100'000'668},
-
3127 STAmount{USD, UINT64_C(13'012'92609877023), -11},
-
3128 ammTokens));
-
3129 }
-
3130 // Payment with the trading fee
-
3131 env(pay(alice, carol, XRP(100)), path(~XRP), sendmax(USD(110)));
-
3132 env.close();
-
3133 // alice pays ~1.011USD in fees, which is ~10 times more
-
3134 // than carol's fee
-
3135 // 100.099431529USD swapped in for 100XRP
-
3136 if (!features[fixAMMv1_1])
-
3137 {
-
3138 BEAST_EXPECT(ammAlice.expectBalances(
-
3139 XRPAmount{13'000'000'668},
-
3140 STAmount{USD, UINT64_C(13'114'03663047264), -11},
-
3141 ammTokens));
-
3142 }
-
3143 else
-
3144 {
-
3145 BEAST_EXPECT(ammAlice.expectBalances(
-
3146 XRPAmount{13'000'000'668},
-
3147 STAmount{USD, UINT64_C(13'114'03663047269), -11},
-
3148 ammTokens));
-
3149 }
-
3150 // Auction slot expired, no discounted fee
-
3151 env.close(seconds(TOTAL_TIME_SLOT_SECS + 1));
-
3152 // clock is parent's based
-
3153 env.close();
-
3154 if (!features[fixAMMv1_1])
-
3155 BEAST_EXPECT(
-
3156 env.balance(carol, USD) ==
-
3157 STAmount(USD, UINT64_C(29'399'00572620545), -11));
-
3158 else
-
3159 BEAST_EXPECT(
-
3160 env.balance(carol, USD) ==
-
3161 STAmount(USD, UINT64_C(29'399'00572620544), -11));
-
3162 ammTokens = ammAlice.getLPTokensBalance();
-
3163 for (int i = 0; i < 10; ++i)
-
3164 {
-
3165 auto const tokens = ammAlice.deposit(carol, USD(100));
-
3166 ammAlice.withdraw(carol, tokens, USD(0));
-
3167 }
-
3168 // carol pays ~9.94USD in fees, which is ~10 times more in
-
3169 // trading fees vs discounted fee.
-
3170 if (!features[fixAMMv1_1])
-
3171 {
-
3172 BEAST_EXPECT(
-
3173 env.balance(carol, USD) ==
-
3174 STAmount(USD, UINT64_C(29'389'06197177128), -11));
-
3175 BEAST_EXPECT(ammAlice.expectBalances(
-
3176 XRPAmount{13'000'000'668},
-
3177 STAmount{USD, UINT64_C(13'123'98038490681), -11},
-
3178 ammTokens));
-
3179 }
-
3180 else
-
3181 {
-
3182 BEAST_EXPECT(
-
3183 env.balance(carol, USD) ==
-
3184 STAmount(USD, UINT64_C(29'389'06197177124), -11));
-
3185 BEAST_EXPECT(ammAlice.expectBalances(
-
3186 XRPAmount{13'000'000'668},
-
3187 STAmount{USD, UINT64_C(13'123'98038490689), -11},
-
3188 ammTokens));
-
3189 }
-
3190 env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110)));
-
3191 env.close();
-
3192 // carol pays ~1.008XRP in trading fee, which is
-
3193 // ~10 times more than the discounted fee.
-
3194 // 99.815876XRP is swapped in for 100USD
-
3195 if (!features[fixAMMv1_1])
-
3196 {
-
3197 BEAST_EXPECT(ammAlice.expectBalances(
-
3198 XRPAmount(13'100'824'790),
-
3199 STAmount{USD, UINT64_C(13'023'98038490681), -11},
-
3200 ammTokens));
-
3201 }
-
3202 else
-
3203 {
+
2835 // auction slot is owned by the creator of the AMM i.e. gw
+
2836 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
+
2837
+
2838 // gw burns all but one of its LPTokens through a bid transaction
+
2839 // this transaction suceeds because the bid price is less than
+
2840 // the total outstanding LPToken balance
+
2841 env(amm.bid({
+
2842 .account = gw,
+
2843 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
+
2844 }),
+
2845 ter(tesSUCCESS))
+
2846 .close();
+
2847
+
2848 // gw must own the auction slot
+
2849 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{999'999}));
+
2850
+
2851 // 999'999 tokens are burned, only 1 LPToken is owned by gw
+
2852 BEAST_EXPECT(
+
2853 amm.expectBalances(XRP(1'000), USD(1'000), IOUAmount{1}));
+
2854
+
2855 // gw owns only 1 LPToken in its balance
+
2856 BEAST_EXPECT(Number{amm.getLPTokensBalance(gw)} == 1);
+
2857
+
2858 // gw attempts to burn the last of its LPTokens in an AMMBid
+
2859 // transaction. This transaction fails because it would burn all
+
2860 // the remaining LPTokens
+
2861 env(amm.bid({
+
2862 .account = gw,
+
2863 .bidMin = 1,
+
2864 }),
+
2865 ter(tecAMM_INVALID_TOKENS));
+
2866 }
+
2867
+
2868 testAMM([&](AMM& ammAlice, Env& env) {
+
2869 // Invalid flags
+
2870 env(ammAlice.bid({
+
2871 .account = carol,
+
2872 .bidMin = 0,
+
2873 .flags = tfWithdrawAll,
+
2874 }),
+
2875 ter(temINVALID_FLAG));
+
2876
+
2877 ammAlice.deposit(carol, 1'000'000);
+
2878 // Invalid Bid price <= 0
+
2879 for (auto bid : {0, -100})
+
2880 {
+
2881 env(ammAlice.bid({
+
2882 .account = carol,
+
2883 .bidMin = bid,
+
2884 }),
+
2885 ter(temBAD_AMOUNT));
+
2886 env(ammAlice.bid({
+
2887 .account = carol,
+
2888 .bidMax = bid,
+
2889 }),
+
2890 ter(temBAD_AMOUNT));
+
2891 }
+
2892
+
2893 // Invlaid Min/Max combination
+
2894 env(ammAlice.bid({
+
2895 .account = carol,
+
2896 .bidMin = 200,
+
2897 .bidMax = 100,
+
2898 }),
+
2899 ter(tecAMM_INVALID_TOKENS));
+
2900
+
2901 // Invalid Account
+
2902 Account bad("bad");
+
2903 env.memoize(bad);
+
2904 env(ammAlice.bid({
+
2905 .account = bad,
+
2906 .bidMax = 100,
+
2907 }),
+
2908 seq(1),
+
2909 ter(terNO_ACCOUNT));
+
2910
+
2911 // Account is not LP
+
2912 Account const dan("dan");
+
2913 env.fund(XRP(1'000), dan);
+
2914 env(ammAlice.bid({
+
2915 .account = dan,
+
2916 .bidMin = 100,
+
2917 }),
+
2918 ter(tecAMM_INVALID_TOKENS));
+
2919 env(ammAlice.bid({
+
2920 .account = dan,
+
2921 }),
+
2922 ter(tecAMM_INVALID_TOKENS));
+
2923
+
2924 // Auth account is invalid.
+
2925 env(ammAlice.bid({
+
2926 .account = carol,
+
2927 .bidMin = 100,
+
2928 .authAccounts = {bob},
+
2929 }),
+
2930 ter(terNO_ACCOUNT));
+
2931
+
2932 // Invalid Assets
+
2933 env(ammAlice.bid({
+
2934 .account = alice,
+
2935 .bidMax = 100,
+
2936 .assets = {{USD, GBP}},
+
2937 }),
+
2938 ter(terNO_AMM));
+
2939
+
2940 // Invalid Min/Max issue
+
2941 env(ammAlice.bid({
+
2942 .account = alice,
+
2943 .bidMax = STAmount{USD, 100},
+
2944 }),
+
2945 ter(temBAD_AMM_TOKENS));
+
2946 env(ammAlice.bid({
+
2947 .account = alice,
+
2948 .bidMin = STAmount{USD, 100},
+
2949 }),
+
2950 ter(temBAD_AMM_TOKENS));
+
2951 });
+
2952
+
2953 // Invalid AMM
+
2954 testAMM([&](AMM& ammAlice, Env& env) {
+
2955 ammAlice.withdrawAll(alice);
+
2956 env(ammAlice.bid({
+
2957 .account = alice,
+
2958 .bidMax = 100,
+
2959 }),
+
2960 ter(terNO_AMM));
+
2961 });
+
2962
+
2963 // More than four Auth accounts.
+
2964 testAMM([&](AMM& ammAlice, Env& env) {
+
2965 Account ed("ed");
+
2966 Account bill("bill");
+
2967 Account scott("scott");
+
2968 Account james("james");
+
2969 env.fund(XRP(1'000), bob, ed, bill, scott, james);
+
2970 env.close();
+
2971 ammAlice.deposit(carol, 1'000'000);
+
2972 env(ammAlice.bid({
+
2973 .account = carol,
+
2974 .bidMin = 100,
+
2975 .authAccounts = {bob, ed, bill, scott, james},
+
2976 }),
+
2977 ter(temMALFORMED));
+
2978 });
+
2979
+
2980 // Bid price exceeds LP owned tokens
+
2981 testAMM([&](AMM& ammAlice, Env& env) {
+
2982 fund(env, gw, {bob}, XRP(1'000), {USD(100)}, Fund::Acct);
+
2983 ammAlice.deposit(carol, 1'000'000);
+
2984 ammAlice.deposit(bob, 10);
+
2985 env(ammAlice.bid({
+
2986 .account = carol,
+
2987 .bidMin = 1'000'001,
+
2988 }),
+
2989 ter(tecAMM_INVALID_TOKENS));
+
2990 env(ammAlice.bid({
+
2991 .account = carol,
+
2992 .bidMax = 1'000'001,
+
2993 }),
+
2994 ter(tecAMM_INVALID_TOKENS));
+
2995 env(ammAlice.bid({
+
2996 .account = carol,
+
2997 .bidMin = 1'000,
+
2998 }));
+
2999 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
+
3000 // Slot purchase price is more than 1000 but bob only has 10 tokens
+
3001 env(ammAlice.bid({
+
3002 .account = bob,
+
3003 }),
+
3004 ter(tecAMM_INVALID_TOKENS));
+
3005 });
+
3006
+
3007 // Bid all tokens, still own the slot
+
3008 {
+
3009 Env env(*this);
+
3010 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'000)});
+
3011 AMM amm(env, gw, XRP(10), USD(1'000));
+
3012 auto const lpIssue = amm.lptIssue();
+
3013 env.trust(STAmount{lpIssue, 100}, alice);
+
3014 env.trust(STAmount{lpIssue, 50}, bob);
+
3015 env(pay(gw, alice, STAmount{lpIssue, 100}));
+
3016 env(pay(gw, bob, STAmount{lpIssue, 50}));
+
3017 env(amm.bid({.account = alice, .bidMin = 100}));
+
3018 // Alice doesn't have any more tokens, but
+
3019 // she still owns the slot.
+
3020 env(amm.bid({
+
3021 .account = bob,
+
3022 .bidMax = 50,
+
3023 }),
+
3024 ter(tecAMM_FAILED));
+
3025 }
+
3026 }
+
3027
+
3028 void
+
3029 testBid(FeatureBitset features)
+
3030 {
+
3031 testcase("Bid");
+
3032 using namespace jtx;
+
3033 using namespace std::chrono;
+
3034
+
3035 // Auction slot initially is owned by AMM creator, who pays 0 price.
+
3036
+
3037 // Bid 110 tokens. Pay bidMin.
+
3038 testAMM(
+
3039 [&](AMM& ammAlice, Env& env) {
+
3040 ammAlice.deposit(carol, 1'000'000);
+
3041 env(ammAlice.bid({.account = carol, .bidMin = 110}));
+
3042 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
+
3043 // 110 tokens are burned.
+
3044 BEAST_EXPECT(ammAlice.expectBalances(
+
3045 XRP(11'000), USD(11'000), IOUAmount{10'999'890, 0}));
+
3046 },
+
3047 std::nullopt,
+
3048 0,
+
3049 std::nullopt,
+
3050 {features});
+
3051
+
3052 // Bid with min/max when the pay price is less than min.
+
3053 testAMM(
+
3054 [&](AMM& ammAlice, Env& env) {
+
3055 ammAlice.deposit(carol, 1'000'000);
+
3056 // Bid exactly 110. Pay 110 because the pay price is < 110.
+
3057 env(ammAlice.bid(
+
3058 {.account = carol, .bidMin = 110, .bidMax = 110}));
+
3059 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
+
3060 BEAST_EXPECT(ammAlice.expectBalances(
+
3061 XRP(11'000), USD(11'000), IOUAmount{10'999'890}));
+
3062 // Bid exactly 180-200. Pay 180 because the pay price is < 180.
+
3063 env(ammAlice.bid(
+
3064 {.account = alice, .bidMin = 180, .bidMax = 200}));
+
3065 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{180}));
+
3066 BEAST_EXPECT(ammAlice.expectBalances(
+
3067 XRP(11'000), USD(11'000), IOUAmount{10'999'814'5, -1}));
+
3068 },
+
3069 std::nullopt,
+
3070 0,
+
3071 std::nullopt,
+
3072 {features});
+
3073
+
3074 // Start bid at bidMin 110.
+
3075 testAMM(
+
3076 [&](AMM& ammAlice, Env& env) {
+
3077 ammAlice.deposit(carol, 1'000'000);
+
3078 // Bid, pay bidMin.
+
3079 env(ammAlice.bid({.account = carol, .bidMin = 110}));
+
3080 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
+
3081
+
3082 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
+
3083 ammAlice.deposit(bob, 1'000'000);
+
3084 // Bid, pay the computed price.
+
3085 env(ammAlice.bid({.account = bob}));
+
3086 BEAST_EXPECT(
+
3087 ammAlice.expectAuctionSlot(0, 0, IOUAmount(1155, -1)));
+
3088
+
3089 // Bid bidMax fails because the computed price is higher.
+
3090 env(ammAlice.bid({
+
3091 .account = carol,
+
3092 .bidMax = 120,
+
3093 }),
+
3094 ter(tecAMM_FAILED));
+
3095 // Bid MaxSlotPrice succeeds - pay computed price
+
3096 env(ammAlice.bid({.account = carol, .bidMax = 600}));
+
3097 BEAST_EXPECT(
+
3098 ammAlice.expectAuctionSlot(0, 0, IOUAmount{121'275, -3}));
+
3099
+
3100 // Bid Min/MaxSlotPrice fails because the computed price is not
+
3101 // in range
+
3102 env(ammAlice.bid({
+
3103 .account = carol,
+
3104 .bidMin = 10,
+
3105 .bidMax = 100,
+
3106 }),
+
3107 ter(tecAMM_FAILED));
+
3108 // Bid Min/MaxSlotPrice succeeds - pay computed price
+
3109 env(ammAlice.bid(
+
3110 {.account = carol, .bidMin = 100, .bidMax = 600}));
+
3111 BEAST_EXPECT(
+
3112 ammAlice.expectAuctionSlot(0, 0, IOUAmount{127'33875, -5}));
+
3113 },
+
3114 std::nullopt,
+
3115 0,
+
3116 std::nullopt,
+
3117 {features});
+
3118
+
3119 // Slot states.
+
3120 testAMM(
+
3121 [&](AMM& ammAlice, Env& env) {
+
3122 ammAlice.deposit(carol, 1'000'000);
+
3123
+
3124 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
+
3125 ammAlice.deposit(bob, 1'000'000);
+
3126 if (!features[fixAMMv1_3])
+
3127 BEAST_EXPECT(ammAlice.expectBalances(
+
3128 XRP(12'000), USD(12'000), IOUAmount{12'000'000, 0}));
+
3129 else
+
3130 BEAST_EXPECT(ammAlice.expectBalances(
+
3131 XRPAmount{12'000'000'001},
+
3132 USD(12'000),
+
3133 IOUAmount{12'000'000, 0}));
+
3134
+
3135 // Initial state. Pay bidMin.
+
3136 env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
+
3137 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
+
3138
+
3139 // 1st Interval after close, price for 0th interval.
+
3140 env(ammAlice.bid({.account = bob}));
+
3141 env.close(seconds(AUCTION_SLOT_INTERVAL_DURATION + 1));
+
3142 BEAST_EXPECT(
+
3143 ammAlice.expectAuctionSlot(0, 1, IOUAmount{1'155, -1}));
+
3144
+
3145 // 10th Interval after close, price for 1st interval.
+
3146 env(ammAlice.bid({.account = carol}));
+
3147 env.close(seconds(10 * AUCTION_SLOT_INTERVAL_DURATION + 1));
+
3148 BEAST_EXPECT(
+
3149 ammAlice.expectAuctionSlot(0, 10, IOUAmount{121'275, -3}));
+
3150
+
3151 // 20th Interval (expired) after close, price for 10th interval.
+
3152 env(ammAlice.bid({.account = bob}));
+
3153 env.close(seconds(
+
3154 AUCTION_SLOT_TIME_INTERVALS *
+
3155 AUCTION_SLOT_INTERVAL_DURATION +
+
3156 1));
+
3157 BEAST_EXPECT(ammAlice.expectAuctionSlot(
+
3158 0, std::nullopt, IOUAmount{127'33875, -5}));
+
3159
+
3160 // 0 Interval.
+
3161 env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
+
3162 BEAST_EXPECT(ammAlice.expectAuctionSlot(
+
3163 0, std::nullopt, IOUAmount{110}));
+
3164 // ~321.09 tokens burnt on bidding fees.
+
3165 if (!features[fixAMMv1_3])
+
3166 BEAST_EXPECT(ammAlice.expectBalances(
+
3167 XRP(12'000),
+
3168 USD(12'000),
+
3169 IOUAmount{11'999'678'91, -2}));
+
3170 else
+
3171 BEAST_EXPECT(ammAlice.expectBalances(
+
3172 XRPAmount{12'000'000'001},
+
3173 USD(12'000),
+
3174 IOUAmount{11'999'678'91, -2}));
+
3175 },
+
3176 std::nullopt,
+
3177 0,
+
3178 std::nullopt,
+
3179 {features});
+
3180
+
3181 // Pool's fee 1%. Bid bidMin.
+
3182 // Auction slot owner and auth account trade at discounted fee -
+
3183 // 1/10 of the trading fee.
+
3184 // Other accounts trade at 1% fee.
+
3185 testAMM(
+
3186 [&](AMM& ammAlice, Env& env) {
+
3187 Account const dan("dan");
+
3188 Account const ed("ed");
+
3189 fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::Acct);
+
3190 ammAlice.deposit(bob, 1'000'000);
+
3191 ammAlice.deposit(ed, 1'000'000);
+
3192 ammAlice.deposit(carol, 500'000);
+
3193 ammAlice.deposit(dan, 500'000);
+
3194 auto ammTokens = ammAlice.getLPTokensBalance();
+
3195 env(ammAlice.bid({
+
3196 .account = carol,
+
3197 .bidMin = 120,
+
3198 .authAccounts = {bob, ed},
+
3199 }));
+
3200 auto const slotPrice = IOUAmount{5'200};
+
3201 ammTokens -= slotPrice;
+
3202 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, slotPrice));
+
3203 if (!features[fixAMMv1_3])
3204 BEAST_EXPECT(ammAlice.expectBalances(
-
3205 XRPAmount(13'100'824'790),
-
3206 STAmount{USD, UINT64_C(13'023'98038490689), -11},
-
3207 ammTokens));
-
3208 }
-
3209 },
-
3210 std::nullopt,
-
3211 1'000,
-
3212 std::nullopt,
-
3213 {features});
-
3214
-
3215 // Bid tiny amount
-
3216 testAMM(
-
3217 [&](AMM& ammAlice, Env& env) {
-
3218 // Bid a tiny amount
-
3219 auto const tiny =
-
3220 Number{STAmount::cMinValue, STAmount::cMinOffset};
-
3221 env(ammAlice.bid(
-
3222 {.account = alice, .bidMin = IOUAmount{tiny}}));
-
3223 // Auction slot purchase price is equal to the tiny amount
-
3224 // since the minSlotPrice is 0 with no trading fee.
-
3225 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
-
3226 // The purchase price is too small to affect the total tokens
-
3227 BEAST_EXPECT(ammAlice.expectBalances(
-
3228 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
3229 // Bid the tiny amount
-
3230 env(ammAlice.bid({
-
3231 .account = alice,
-
3232 .bidMin =
-
3233 IOUAmount{STAmount::cMinValue, STAmount::cMinOffset},
-
3234 }));
-
3235 // Pay slightly higher price
-
3236 BEAST_EXPECT(ammAlice.expectAuctionSlot(
-
3237 0, 0, IOUAmount{tiny * Number{105, -2}}));
-
3238 // The purchase price is still too small to affect the total
-
3239 // tokens
-
3240 BEAST_EXPECT(ammAlice.expectBalances(
-
3241 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
3242 },
-
3243 std::nullopt,
-
3244 0,
-
3245 std::nullopt,
-
3246 {features});
-
3247
-
3248 // Reset auth account
-
3249 testAMM(
-
3250 [&](AMM& ammAlice, Env& env) {
-
3251 env(ammAlice.bid({
-
3252 .account = alice,
-
3253 .bidMin = IOUAmount{100},
-
3254 .authAccounts = {carol},
-
3255 }));
-
3256 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
-
3257 env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}}));
-
3258 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
-
3259 Account bob("bob");
-
3260 Account dan("dan");
-
3261 fund(env, {bob, dan}, XRP(1'000));
-
3262 env(ammAlice.bid({
-
3263 .account = alice,
-
3264 .bidMin = IOUAmount{100},
-
3265 .authAccounts = {bob, dan},
-
3266 }));
-
3267 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
-
3268 },
-
3269 std::nullopt,
-
3270 0,
-
3271 std::nullopt,
-
3272 {features});
-
3273
-
3274 // Bid all tokens, still own the slot and trade at a discount
-
3275 {
-
3276 Env env(*this, features);
-
3277 fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
-
3278 AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
-
3279 auto const lpIssue = amm.lptIssue();
-
3280 env.trust(STAmount{lpIssue, 500}, alice);
-
3281 env.trust(STAmount{lpIssue, 50}, bob);
-
3282 env(pay(gw, alice, STAmount{lpIssue, 500}));
-
3283 env(pay(gw, bob, STAmount{lpIssue, 50}));
-
3284 // Alice doesn't have anymore lp tokens
-
3285 env(amm.bid({.account = alice, .bidMin = 500}));
-
3286 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{500}));
-
3287 BEAST_EXPECT(expectLine(env, alice, STAmount{lpIssue, 0}));
-
3288 // But trades with the discounted fee since she still owns the slot.
-
3289 // Alice pays 10011 drops in fees
-
3290 env(pay(alice, bob, USD(10)), path(~USD), sendmax(XRP(11)));
-
3291 BEAST_EXPECT(amm.expectBalances(
-
3292 XRPAmount{1'010'010'011},
-
3293 USD(1'000),
-
3294 IOUAmount{1'004'487'562112089, -9}));
-
3295 // Bob pays the full fee ~0.1USD
-
3296 env(pay(bob, alice, XRP(10)), path(~XRP), sendmax(USD(11)));
-
3297 if (!features[fixAMMv1_1])
-
3298 {
-
3299 BEAST_EXPECT(amm.expectBalances(
-
3300 XRPAmount{1'000'010'011},
-
3301 STAmount{USD, UINT64_C(1'010'10090898081), -11},
-
3302 IOUAmount{1'004'487'562112089, -9}));
-
3303 }
-
3304 else
-
3305 {
-
3306 BEAST_EXPECT(amm.expectBalances(
-
3307 XRPAmount{1'000'010'011},
-
3308 STAmount{USD, UINT64_C(1'010'100908980811), -12},
-
3309 IOUAmount{1'004'487'562112089, -9}));
-
3310 }
-
3311 }
-
3312
-
3313 // preflight tests
-
3314 {
-
3315 Env env(*this, features);
-
3316 auto const baseFee = env.current()->fees().base;
-
3317
-
3318 fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
-
3319 AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
-
3320 Json::Value tx = amm.bid({.account = alice, .bidMin = 500});
-
3321
-
3322 {
-
3323 auto jtx = env.jt(tx, seq(1), fee(baseFee));
-
3324 env.app().config().features.erase(featureAMM);
-
3325 PreflightContext pfctx(
-
3326 env.app(),
-
3327 *jtx.stx,
-
3328 env.current()->rules(),
-
3329 tapNONE,
-
3330 env.journal);
-
3331 auto pf = AMMBid::preflight(pfctx);
-
3332 BEAST_EXPECT(pf == temDISABLED);
-
3333 env.app().config().features.insert(featureAMM);
-
3334 }
-
3335
-
3336 {
-
3337 auto jtx = env.jt(tx, seq(1), fee(baseFee));
-
3338 jtx.jv["TxnSignature"] = "deadbeef";
-
3339 jtx.stx = env.ust(jtx);
-
3340 PreflightContext pfctx(
-
3341 env.app(),
-
3342 *jtx.stx,
-
3343 env.current()->rules(),
-
3344 tapNONE,
-
3345 env.journal);
-
3346 auto pf = AMMBid::preflight(pfctx);
-
3347 BEAST_EXPECT(pf != tesSUCCESS);
-
3348 }
-
3349
-
3350 {
-
3351 auto jtx = env.jt(tx, seq(1), fee(baseFee));
-
3352 jtx.jv["Asset2"]["currency"] = "XRP";
-
3353 jtx.jv["Asset2"].removeMember("issuer");
-
3354 jtx.stx = env.ust(jtx);
-
3355 PreflightContext pfctx(
-
3356 env.app(),
-
3357 *jtx.stx,
-
3358 env.current()->rules(),
-
3359 tapNONE,
-
3360 env.journal);
-
3361 auto pf = AMMBid::preflight(pfctx);
-
3362 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
-
3363 }
-
3364 }
-
3365 }
-
3366
-
3367 void
-
3368 testInvalidAMMPayment()
-
3369 {
-
3370 testcase("Invalid AMM Payment");
-
3371 using namespace jtx;
-
3372 using namespace std::chrono;
-
3373 using namespace std::literals::chrono_literals;
-
3374
-
3375 // Can't pay into AMM account.
-
3376 // Can't pay out since there is no keys
-
3377 for (auto const& acct : {gw, alice})
-
3378 {
-
3379 {
-
3380 Env env(*this);
-
3381 fund(env, gw, {alice, carol}, XRP(1'000), {USD(100)});
-
3382 // XRP balance is below reserve
-
3383 AMM ammAlice(env, acct, XRP(10), USD(10));
-
3384 // Pay below reserve
-
3385 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
-
3386 ter(tecNO_PERMISSION));
-
3387 // Pay above reserve
-
3388 env(pay(carol, ammAlice.ammAccount(), XRP(300)),
-
3389 ter(tecNO_PERMISSION));
-
3390 // Pay IOU
-
3391 env(pay(carol, ammAlice.ammAccount(), USD(10)),
-
3392 ter(tecNO_PERMISSION));
-
3393 }
-
3394 {
-
3395 Env env(*this);
-
3396 fund(env, gw, {alice, carol}, XRP(10'000'000), {USD(10'000)});
-
3397 // XRP balance is above reserve
-
3398 AMM ammAlice(env, acct, XRP(1'000'000), USD(100));
-
3399 // Pay below reserve
-
3400 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
-
3401 ter(tecNO_PERMISSION));
-
3402 // Pay above reserve
-
3403 env(pay(carol, ammAlice.ammAccount(), XRP(1'000'000)),
-
3404 ter(tecNO_PERMISSION));
-
3405 }
-
3406 }
-
3407
-
3408 // Can't pay into AMM with escrow.
-
3409 testAMM([&](AMM& ammAlice, Env& env) {
-
3410 auto const baseFee = env.current()->fees().base;
-
3411 env(escrow(carol, ammAlice.ammAccount(), XRP(1)),
-
3412 condition(cb1),
-
3413 finish_time(env.now() + 1s),
-
3414 cancel_time(env.now() + 2s),
-
3415 fee(baseFee * 150),
-
3416 ter(tecNO_PERMISSION));
-
3417 });
-
3418
-
3419 // Can't pay into AMM with paychan.
-
3420 testAMM([&](AMM& ammAlice, Env& env) {
-
3421 auto const pk = carol.pk();
-
3422 auto const settleDelay = 100s;
-
3423 NetClock::time_point const cancelAfter =
-
3424 env.current()->info().parentCloseTime + 200s;
-
3425 env(create(
-
3426 carol,
-
3427 ammAlice.ammAccount(),
-
3428 XRP(1'000),
-
3429 settleDelay,
-
3430 pk,
-
3431 cancelAfter),
-
3432 ter(tecNO_PERMISSION));
-
3433 });
-
3434
-
3435 // Can't pay into AMM with checks.
-
3436 testAMM([&](AMM& ammAlice, Env& env) {
-
3437 env(check::create(env.master.id(), ammAlice.ammAccount(), XRP(100)),
-
3438 ter(tecNO_PERMISSION));
-
3439 });
-
3440
-
3441 // Pay amounts close to one side of the pool
-
3442 testAMM(
-
3443 [&](AMM& ammAlice, Env& env) {
-
3444 // Can't consume whole pool
-
3445 env(pay(alice, carol, USD(100)),
-
3446 path(~USD),
-
3447 sendmax(XRP(1'000'000'000)),
-
3448 ter(tecPATH_PARTIAL));
-
3449 env(pay(alice, carol, XRP(100)),
-
3450 path(~XRP),
-
3451 sendmax(USD(1'000'000'000)),
-
3452 ter(tecPATH_PARTIAL));
-
3453 // Overflow
-
3454 env(pay(alice,
-
3455 carol,
-
3456 STAmount{USD, UINT64_C(99'999999999), -9}),
-
3457 path(~USD),
-
3458 sendmax(XRP(1'000'000'000)),
-
3459 ter(tecPATH_PARTIAL));
-
3460 env(pay(alice,
-
3461 carol,
-
3462 STAmount{USD, UINT64_C(999'99999999), -8}),
-
3463 path(~USD),
-
3464 sendmax(XRP(1'000'000'000)),
-
3465 ter(tecPATH_PARTIAL));
-
3466 env(pay(alice, carol, STAmount{xrpIssue(), 99'999'999}),
-
3467 path(~XRP),
-
3468 sendmax(USD(1'000'000'000)),
-
3469 ter(tecPATH_PARTIAL));
-
3470 // Sender doesn't have enough funds
-
3471 env(pay(alice, carol, USD(99.99)),
-
3472 path(~USD),
-
3473 sendmax(XRP(1'000'000'000)),
-
3474 ter(tecPATH_PARTIAL));
-
3475 env(pay(alice, carol, STAmount{xrpIssue(), 99'990'000}),
-
3476 path(~XRP),
-
3477 sendmax(USD(1'000'000'000)),
-
3478 ter(tecPATH_PARTIAL));
-
3479 },
-
3480 {{XRP(100), USD(100)}});
-
3481
-
3482 // Globally frozen
-
3483 testAMM([&](AMM& ammAlice, Env& env) {
-
3484 env(fset(gw, asfGlobalFreeze));
-
3485 env.close();
-
3486 env(pay(alice, carol, USD(1)),
-
3487 path(~USD),
-
3488 txflags(tfPartialPayment | tfNoRippleDirect),
-
3489 sendmax(XRP(10)),
-
3490 ter(tecPATH_DRY));
-
3491 env(pay(alice, carol, XRP(1)),
-
3492 path(~XRP),
-
3493 txflags(tfPartialPayment | tfNoRippleDirect),
-
3494 sendmax(USD(10)),
-
3495 ter(tecPATH_DRY));
-
3496 });
-
3497
-
3498 // Individually frozen AMM
-
3499 testAMM([&](AMM& ammAlice, Env& env) {
-
3500 env(trust(
-
3501 gw,
-
3502 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
-
3503 tfSetFreeze));
-
3504 env.close();
-
3505 env(pay(alice, carol, USD(1)),
-
3506 path(~USD),
-
3507 txflags(tfPartialPayment | tfNoRippleDirect),
-
3508 sendmax(XRP(10)),
-
3509 ter(tecPATH_DRY));
-
3510 env(pay(alice, carol, XRP(1)),
-
3511 path(~XRP),
-
3512 txflags(tfPartialPayment | tfNoRippleDirect),
-
3513 sendmax(USD(10)),
-
3514 ter(tecPATH_DRY));
-
3515 });
+
3205 XRP(13'000), USD(13'000), ammTokens));
+
3206 else
+
3207 BEAST_EXPECT(ammAlice.expectBalances(
+
3208 XRPAmount{13'000'000'003}, USD(13'000), ammTokens));
+
3209 // Discounted trade
+
3210 for (int i = 0; i < 10; ++i)
+
3211 {
+
3212 auto tokens = ammAlice.deposit(carol, USD(100));
+
3213 ammAlice.withdraw(carol, tokens, USD(0));
+
3214 tokens = ammAlice.deposit(bob, USD(100));
+
3215 ammAlice.withdraw(bob, tokens, USD(0));
+
3216 tokens = ammAlice.deposit(ed, USD(100));
+
3217 ammAlice.withdraw(ed, tokens, USD(0));
+
3218 }
+
3219 // carol, bob, and ed pay ~0.99USD in fees.
+
3220 if (!features[fixAMMv1_1])
+
3221 {
+
3222 BEAST_EXPECT(
+
3223 env.balance(carol, USD) ==
+
3224 STAmount(USD, UINT64_C(29'499'00572620545), -11));
+
3225 BEAST_EXPECT(
+
3226 env.balance(bob, USD) ==
+
3227 STAmount(USD, UINT64_C(18'999'00572616195), -11));
+
3228 BEAST_EXPECT(
+
3229 env.balance(ed, USD) ==
+
3230 STAmount(USD, UINT64_C(18'999'00572611841), -11));
+
3231 // USD pool is slightly higher because of the fees.
+
3232 BEAST_EXPECT(ammAlice.expectBalances(
+
3233 XRP(13'000),
+
3234 STAmount(USD, UINT64_C(13'002'98282151419), -11),
+
3235 ammTokens));
+
3236 }
+
3237 else
+
3238 {
+
3239 BEAST_EXPECT(
+
3240 env.balance(carol, USD) ==
+
3241 STAmount(USD, UINT64_C(29'499'00572620544), -11));
+
3242 BEAST_EXPECT(
+
3243 env.balance(bob, USD) ==
+
3244 STAmount(USD, UINT64_C(18'999'00572616194), -11));
+
3245 BEAST_EXPECT(
+
3246 env.balance(ed, USD) ==
+
3247 STAmount(USD, UINT64_C(18'999'0057261184), -10));
+
3248 // USD pool is slightly higher because of the fees.
+
3249 if (!features[fixAMMv1_3])
+
3250 BEAST_EXPECT(ammAlice.expectBalances(
+
3251 XRP(13'000),
+
3252 STAmount(USD, UINT64_C(13'002'98282151422), -11),
+
3253 ammTokens));
+
3254 else
+
3255 BEAST_EXPECT(ammAlice.expectBalances(
+
3256 XRPAmount{13'000'000'003},
+
3257 STAmount(USD, UINT64_C(13'002'98282151422), -11),
+
3258 ammTokens));
+
3259 }
+
3260 ammTokens = ammAlice.getLPTokensBalance();
+
3261 // Trade with the fee
+
3262 for (int i = 0; i < 10; ++i)
+
3263 {
+
3264 auto const tokens = ammAlice.deposit(dan, USD(100));
+
3265 ammAlice.withdraw(dan, tokens, USD(0));
+
3266 }
+
3267 // dan pays ~9.94USD, which is ~10 times more in fees than
+
3268 // carol, bob, ed. the discounted fee is 10 times less
+
3269 // than the trading fee.
+
3270 if (!features[fixAMMv1_1])
+
3271 {
+
3272 BEAST_EXPECT(
+
3273 env.balance(dan, USD) ==
+
3274 STAmount(USD, UINT64_C(19'490'056722744), -9));
+
3275 // USD pool gains more in dan's fees.
+
3276 BEAST_EXPECT(ammAlice.expectBalances(
+
3277 XRP(13'000),
+
3278 STAmount{USD, UINT64_C(13'012'92609877019), -11},
+
3279 ammTokens));
+
3280 // Discounted fee payment
+
3281 ammAlice.deposit(carol, USD(100));
+
3282 ammTokens = ammAlice.getLPTokensBalance();
+
3283 BEAST_EXPECT(ammAlice.expectBalances(
+
3284 XRP(13'000),
+
3285 STAmount{USD, UINT64_C(13'112'92609877019), -11},
+
3286 ammTokens));
+
3287 env(pay(carol, bob, USD(100)),
+
3288 path(~USD),
+
3289 sendmax(XRP(110)));
+
3290 env.close();
+
3291 // carol pays 100000 drops in fees
+
3292 // 99900668XRP swapped in for 100USD
+
3293 BEAST_EXPECT(ammAlice.expectBalances(
+
3294 XRPAmount{13'100'000'668},
+
3295 STAmount{USD, UINT64_C(13'012'92609877019), -11},
+
3296 ammTokens));
+
3297 }
+
3298 else
+
3299 {
+
3300 if (!features[fixAMMv1_3])
+
3301 BEAST_EXPECT(
+
3302 env.balance(dan, USD) ==
+
3303 STAmount(USD, UINT64_C(19'490'05672274399), -11));
+
3304 else
+
3305 BEAST_EXPECT(
+
3306 env.balance(dan, USD) ==
+
3307 STAmount(USD, UINT64_C(19'490'05672274398), -11));
+
3308 // USD pool gains more in dan's fees.
+
3309 if (!features[fixAMMv1_3])
+
3310 BEAST_EXPECT(ammAlice.expectBalances(
+
3311 XRP(13'000),
+
3312 STAmount{USD, UINT64_C(13'012'92609877023), -11},
+
3313 ammTokens));
+
3314 else
+
3315 BEAST_EXPECT(ammAlice.expectBalances(
+
3316 XRPAmount{13'000'000'003},
+
3317 STAmount{USD, UINT64_C(13'012'92609877024), -11},
+
3318 ammTokens));
+
3319 // Discounted fee payment
+
3320 ammAlice.deposit(carol, USD(100));
+
3321 ammTokens = ammAlice.getLPTokensBalance();
+
3322 if (!features[fixAMMv1_3])
+
3323 BEAST_EXPECT(ammAlice.expectBalances(
+
3324 XRP(13'000),
+
3325 STAmount{USD, UINT64_C(13'112'92609877023), -11},
+
3326 ammTokens));
+
3327 else
+
3328 BEAST_EXPECT(ammAlice.expectBalances(
+
3329 XRPAmount{13'000'000'003},
+
3330 STAmount{USD, UINT64_C(13'112'92609877024), -11},
+
3331 ammTokens));
+
3332 env(pay(carol, bob, USD(100)),
+
3333 path(~USD),
+
3334 sendmax(XRP(110)));
+
3335 env.close();
+
3336 // carol pays 100000 drops in fees
+
3337 // 99900668XRP swapped in for 100USD
+
3338 if (!features[fixAMMv1_3])
+
3339 BEAST_EXPECT(ammAlice.expectBalances(
+
3340 XRPAmount{13'100'000'668},
+
3341 STAmount{USD, UINT64_C(13'012'92609877023), -11},
+
3342 ammTokens));
+
3343 else
+
3344 BEAST_EXPECT(ammAlice.expectBalances(
+
3345 XRPAmount{13'100'000'671},
+
3346 STAmount{USD, UINT64_C(13'012'92609877024), -11},
+
3347 ammTokens));
+
3348 }
+
3349 // Payment with the trading fee
+
3350 env(pay(alice, carol, XRP(100)), path(~XRP), sendmax(USD(110)));
+
3351 env.close();
+
3352 // alice pays ~1.011USD in fees, which is ~10 times more
+
3353 // than carol's fee
+
3354 // 100.099431529USD swapped in for 100XRP
+
3355 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
+
3356 {
+
3357 BEAST_EXPECT(ammAlice.expectBalances(
+
3358 XRPAmount{13'000'000'668},
+
3359 STAmount{USD, UINT64_C(13'114'03663047264), -11},
+
3360 ammTokens));
+
3361 }
+
3362 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
+
3363 {
+
3364 BEAST_EXPECT(ammAlice.expectBalances(
+
3365 XRPAmount{13'000'000'668},
+
3366 STAmount{USD, UINT64_C(13'114'03663047269), -11},
+
3367 ammTokens));
+
3368 }
+
3369 else
+
3370 {
+
3371 BEAST_EXPECT(ammAlice.expectBalances(
+
3372 XRPAmount{13'000'000'671},
+
3373 STAmount{USD, UINT64_C(13'114'03663044937), -11},
+
3374 ammTokens));
+
3375 }
+
3376 // Auction slot expired, no discounted fee
+
3377 env.close(seconds(TOTAL_TIME_SLOT_SECS + 1));
+
3378 // clock is parent's based
+
3379 env.close();
+
3380 if (!features[fixAMMv1_1])
+
3381 BEAST_EXPECT(
+
3382 env.balance(carol, USD) ==
+
3383 STAmount(USD, UINT64_C(29'399'00572620545), -11));
+
3384 else if (!features[fixAMMv1_3])
+
3385 BEAST_EXPECT(
+
3386 env.balance(carol, USD) ==
+
3387 STAmount(USD, UINT64_C(29'399'00572620544), -11));
+
3388 ammTokens = ammAlice.getLPTokensBalance();
+
3389 for (int i = 0; i < 10; ++i)
+
3390 {
+
3391 auto const tokens = ammAlice.deposit(carol, USD(100));
+
3392 ammAlice.withdraw(carol, tokens, USD(0));
+
3393 }
+
3394 // carol pays ~9.94USD in fees, which is ~10 times more in
+
3395 // trading fees vs discounted fee.
+
3396 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
+
3397 {
+
3398 BEAST_EXPECT(
+
3399 env.balance(carol, USD) ==
+
3400 STAmount(USD, UINT64_C(29'389'06197177128), -11));
+
3401 BEAST_EXPECT(ammAlice.expectBalances(
+
3402 XRPAmount{13'000'000'668},
+
3403 STAmount{USD, UINT64_C(13'123'98038490681), -11},
+
3404 ammTokens));
+
3405 }
+
3406 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
+
3407 {
+
3408 BEAST_EXPECT(
+
3409 env.balance(carol, USD) ==
+
3410 STAmount(USD, UINT64_C(29'389'06197177124), -11));
+
3411 BEAST_EXPECT(ammAlice.expectBalances(
+
3412 XRPAmount{13'000'000'668},
+
3413 STAmount{USD, UINT64_C(13'123'98038490689), -11},
+
3414 ammTokens));
+
3415 }
+
3416 else
+
3417 {
+
3418 BEAST_EXPECT(
+
3419 env.balance(carol, USD) ==
+
3420 STAmount(USD, UINT64_C(29'389'06197177129), -11));
+
3421 BEAST_EXPECT(ammAlice.expectBalances(
+
3422 XRPAmount{13'000'000'671},
+
3423 STAmount{USD, UINT64_C(13'123'98038488352), -11},
+
3424 ammTokens));
+
3425 }
+
3426 env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110)));
+
3427 env.close();
+
3428 // carol pays ~1.008XRP in trading fee, which is
+
3429 // ~10 times more than the discounted fee.
+
3430 // 99.815876XRP is swapped in for 100USD
+
3431 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
+
3432 {
+
3433 BEAST_EXPECT(ammAlice.expectBalances(
+
3434 XRPAmount(13'100'824'790),
+
3435 STAmount{USD, UINT64_C(13'023'98038490681), -11},
+
3436 ammTokens));
+
3437 }
+
3438 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
+
3439 {
+
3440 BEAST_EXPECT(ammAlice.expectBalances(
+
3441 XRPAmount(13'100'824'790),
+
3442 STAmount{USD, UINT64_C(13'023'98038490689), -11},
+
3443 ammTokens));
+
3444 }
+
3445 else
+
3446 {
+
3447 BEAST_EXPECT(ammAlice.expectBalances(
+
3448 XRPAmount(13'100'824'793),
+
3449 STAmount{USD, UINT64_C(13'023'98038488352), -11},
+
3450 ammTokens));
+
3451 }
+
3452 },
+
3453 std::nullopt,
+
3454 1'000,
+
3455 std::nullopt,
+
3456 {features});
+
3457
+
3458 // Bid tiny amount
+
3459 testAMM(
+
3460 [&](AMM& ammAlice, Env& env) {
+
3461 // Bid a tiny amount
+
3462 auto const tiny =
+
3463 Number{STAmount::cMinValue, STAmount::cMinOffset};
+
3464 env(ammAlice.bid(
+
3465 {.account = alice, .bidMin = IOUAmount{tiny}}));
+
3466 // Auction slot purchase price is equal to the tiny amount
+
3467 // since the minSlotPrice is 0 with no trading fee.
+
3468 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
+
3469 // The purchase price is too small to affect the total tokens
+
3470 BEAST_EXPECT(ammAlice.expectBalances(
+
3471 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
3472 // Bid the tiny amount
+
3473 env(ammAlice.bid({
+
3474 .account = alice,
+
3475 .bidMin =
+
3476 IOUAmount{STAmount::cMinValue, STAmount::cMinOffset},
+
3477 }));
+
3478 // Pay slightly higher price
+
3479 BEAST_EXPECT(ammAlice.expectAuctionSlot(
+
3480 0, 0, IOUAmount{tiny * Number{105, -2}}));
+
3481 // The purchase price is still too small to affect the total
+
3482 // tokens
+
3483 BEAST_EXPECT(ammAlice.expectBalances(
+
3484 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
3485 },
+
3486 std::nullopt,
+
3487 0,
+
3488 std::nullopt,
+
3489 {features});
+
3490
+
3491 // Reset auth account
+
3492 testAMM(
+
3493 [&](AMM& ammAlice, Env& env) {
+
3494 env(ammAlice.bid({
+
3495 .account = alice,
+
3496 .bidMin = IOUAmount{100},
+
3497 .authAccounts = {carol},
+
3498 }));
+
3499 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
+
3500 env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}}));
+
3501 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
+
3502 Account bob("bob");
+
3503 Account dan("dan");
+
3504 fund(env, {bob, dan}, XRP(1'000));
+
3505 env(ammAlice.bid({
+
3506 .account = alice,
+
3507 .bidMin = IOUAmount{100},
+
3508 .authAccounts = {bob, dan},
+
3509 }));
+
3510 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
+
3511 },
+
3512 std::nullopt,
+
3513 0,
+
3514 std::nullopt,
+
3515 {features});
3516
-
3517 // Individually frozen accounts
-
3518 testAMM([&](AMM& ammAlice, Env& env) {
-
3519 env(trust(gw, carol["USD"](0), tfSetFreeze));
-
3520 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
3521 env.close();
-
3522 env(pay(alice, carol, XRP(1)),
-
3523 path(~XRP),
-
3524 sendmax(USD(10)),
-
3525 txflags(tfNoRippleDirect | tfPartialPayment),
-
3526 ter(tecPATH_DRY));
-
3527 });
-
3528 }
-
3529
-
3530 void
-
3531 testBasicPaymentEngine(FeatureBitset features)
-
3532 {
-
3533 testcase("Basic Payment");
-
3534 using namespace jtx;
-
3535
-
3536 // Payment 100USD for 100XRP.
-
3537 // Force one path with tfNoRippleDirect.
-
3538 testAMM(
-
3539 [&](AMM& ammAlice, Env& env) {
-
3540 env.fund(jtx::XRP(30'000), bob);
-
3541 env.close();
-
3542 env(pay(bob, carol, USD(100)),
-
3543 path(~USD),
-
3544 sendmax(XRP(100)),
-
3545 txflags(tfNoRippleDirect));
-
3546 env.close();
-
3547 BEAST_EXPECT(ammAlice.expectBalances(
-
3548 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3549 // Initial balance 30,000 + 100
-
3550 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
3551 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
-
3552 BEAST_EXPECT(expectLedgerEntryRoot(
-
3553 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
-
3554 },
-
3555 {{XRP(10'000), USD(10'100)}},
-
3556 0,
-
3557 std::nullopt,
-
3558 {features});
-
3559
-
3560 // Payment 100USD for 100XRP, use default path.
-
3561 testAMM(
-
3562 [&](AMM& ammAlice, Env& env) {
-
3563 env.fund(jtx::XRP(30'000), bob);
-
3564 env.close();
-
3565 env(pay(bob, carol, USD(100)), sendmax(XRP(100)));
-
3566 env.close();
-
3567 BEAST_EXPECT(ammAlice.expectBalances(
-
3568 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3569 // Initial balance 30,000 + 100
-
3570 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
3571 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
-
3572 BEAST_EXPECT(expectLedgerEntryRoot(
-
3573 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
-
3574 },
-
3575 {{XRP(10'000), USD(10'100)}},
-
3576 0,
-
3577 std::nullopt,
-
3578 {features});
-
3579
-
3580 // This payment is identical to above. While it has
-
3581 // both default path and path, activeStrands has one path.
-
3582 testAMM(
-
3583 [&](AMM& ammAlice, Env& env) {
-
3584 env.fund(jtx::XRP(30'000), bob);
-
3585 env.close();
-
3586 env(pay(bob, carol, USD(100)), path(~USD), sendmax(XRP(100)));
-
3587 env.close();
-
3588 BEAST_EXPECT(ammAlice.expectBalances(
-
3589 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3590 // Initial balance 30,000 + 100
-
3591 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
3592 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
-
3593 BEAST_EXPECT(expectLedgerEntryRoot(
-
3594 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
-
3595 },
-
3596 {{XRP(10'000), USD(10'100)}},
-
3597 0,
-
3598 std::nullopt,
-
3599 {features});
-
3600
-
3601 // Payment with limitQuality set.
-
3602 testAMM(
-
3603 [&](AMM& ammAlice, Env& env) {
-
3604 env.fund(jtx::XRP(30'000), bob);
-
3605 env.close();
-
3606 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
-
3607 // would have been sent has it not been for limitQuality.
-
3608 env(pay(bob, carol, USD(100)),
-
3609 path(~USD),
-
3610 sendmax(XRP(100)),
-
3611 txflags(
-
3612 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
3613 env.close();
-
3614 BEAST_EXPECT(ammAlice.expectBalances(
-
3615 XRP(10'010), USD(10'000), ammAlice.tokens()));
-
3616 // Initial balance 30,000 + 10(limited by limitQuality)
-
3617 BEAST_EXPECT(expectLine(env, carol, USD(30'010)));
-
3618 // Initial balance 30,000 - 10(limited by limitQuality) - 10(tx
-
3619 // fee)
-
3620 BEAST_EXPECT(expectLedgerEntryRoot(
-
3621 env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
-
3622
-
3623 // Fails because of limitQuality. Would have sent
-
3624 // ~98.91USD/110XRP has it not been for limitQuality.
-
3625 env(pay(bob, carol, USD(100)),
-
3626 path(~USD),
-
3627 sendmax(XRP(100)),
-
3628 txflags(
-
3629 tfNoRippleDirect | tfPartialPayment | tfLimitQuality),
-
3630 ter(tecPATH_DRY));
-
3631 env.close();
-
3632 },
-
3633 {{XRP(10'000), USD(10'010)}},
-
3634 0,
-
3635 std::nullopt,
-
3636 {features});
-
3637
-
3638 // Payment with limitQuality and transfer fee set.
-
3639 testAMM(
-
3640 [&](AMM& ammAlice, Env& env) {
-
3641 env(rate(gw, 1.1));
-
3642 env.close();
-
3643 env.fund(jtx::XRP(30'000), bob);
-
3644 env.close();
-
3645 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
-
3646 // would have been sent has it not been for limitQuality and
-
3647 // the transfer fee.
-
3648 env(pay(bob, carol, USD(100)),
-
3649 path(~USD),
-
3650 sendmax(XRP(110)),
-
3651 txflags(
-
3652 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
3653 env.close();
-
3654 BEAST_EXPECT(ammAlice.expectBalances(
-
3655 XRP(10'010), USD(10'000), ammAlice.tokens()));
-
3656 // 10USD - 10% transfer fee
-
3657 BEAST_EXPECT(expectLine(
-
3658 env,
-
3659 carol,
-
3660 STAmount{USD, UINT64_C(30'009'09090909091), -11}));
-
3661 BEAST_EXPECT(expectLedgerEntryRoot(
-
3662 env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
-
3663 },
-
3664 {{XRP(10'000), USD(10'010)}},
-
3665 0,
-
3666 std::nullopt,
-
3667 {features});
-
3668
-
3669 // Fail when partial payment is not set.
-
3670 testAMM(
-
3671 [&](AMM& ammAlice, Env& env) {
-
3672 env.fund(jtx::XRP(30'000), bob);
-
3673 env.close();
-
3674 env(pay(bob, carol, USD(100)),
-
3675 path(~USD),
-
3676 sendmax(XRP(100)),
-
3677 txflags(tfNoRippleDirect),
-
3678 ter(tecPATH_PARTIAL));
-
3679 },
-
3680 {{XRP(10'000), USD(10'000)}},
-
3681 0,
-
3682 std::nullopt,
-
3683 {features});
-
3684
-
3685 // Non-default path (with AMM) has a better quality than default path.
-
3686 // The max possible liquidity is taken out of non-default
-
3687 // path ~29.9XRP/29.9EUR, ~29.9EUR/~29.99USD. The rest
-
3688 // is taken from the offer.
-
3689 {
-
3690 Env env(*this, features);
-
3691 fund(
-
3692 env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
-
3693 env.close();
-
3694 env.fund(XRP(1'000), bob);
-
3695 env.close();
-
3696 auto ammEUR_XRP = AMM(env, alice, XRP(10'000), EUR(10'000));
-
3697 auto ammUSD_EUR = AMM(env, alice, EUR(10'000), USD(10'000));
-
3698 env(offer(alice, XRP(101), USD(100)), txflags(tfPassive));
-
3699 env.close();
-
3700 env(pay(bob, carol, USD(100)),
-
3701 path(~EUR, ~USD),
-
3702 sendmax(XRP(102)),
-
3703 txflags(tfPartialPayment));
-
3704 env.close();
-
3705 BEAST_EXPECT(ammEUR_XRP.expectBalances(
-
3706 XRPAmount(10'030'082'730),
-
3707 STAmount(EUR, UINT64_C(9'970'007498125468), -12),
-
3708 ammEUR_XRP.tokens()));
-
3709 if (!features[fixAMMv1_1])
-
3710 {
-
3711 BEAST_EXPECT(ammUSD_EUR.expectBalances(
-
3712 STAmount(USD, UINT64_C(9'970'097277662122), -12),
-
3713 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
-
3714 ammUSD_EUR.tokens()));
-
3715
-
3716 // fixReducedOffersV2 changes the expected results slightly.
-
3717 Amounts const expectedAmounts =
-
3718 env.closed()->rules().enabled(fixReducedOffersV2)
-
3719 ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233787816), -14)}
-
3720 : Amounts{
-
3721 XRPAmount(30'201'749),
-
3722 STAmount(USD, UINT64_C(29'90272233787818), -14)};
-
3723
-
3724 BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}}));
-
3725 }
-
3726 else
-
3727 {
-
3728 BEAST_EXPECT(ammUSD_EUR.expectBalances(
-
3729 STAmount(USD, UINT64_C(9'970'097277662172), -12),
-
3730 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
-
3731 ammUSD_EUR.tokens()));
-
3732
-
3733 // fixReducedOffersV2 changes the expected results slightly.
-
3734 Amounts const expectedAmounts =
-
3735 env.closed()->rules().enabled(fixReducedOffersV2)
-
3736 ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233782839), -14)}
-
3737 : Amounts{
-
3738 XRPAmount(30'201'749),
-
3739 STAmount(USD, UINT64_C(29'90272233782840), -14)};
+
3517 // Bid all tokens, still own the slot and trade at a discount
+
3518 {
+
3519 Env env(*this, features);
+
3520 fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
+
3521 AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
+
3522 auto const lpIssue = amm.lptIssue();
+
3523 env.trust(STAmount{lpIssue, 500}, alice);
+
3524 env.trust(STAmount{lpIssue, 50}, bob);
+
3525 env(pay(gw, alice, STAmount{lpIssue, 500}));
+
3526 env(pay(gw, bob, STAmount{lpIssue, 50}));
+
3527 // Alice doesn't have anymore lp tokens
+
3528 env(amm.bid({.account = alice, .bidMin = 500}));
+
3529 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{500}));
+
3530 BEAST_EXPECT(expectLine(env, alice, STAmount{lpIssue, 0}));
+
3531 // But trades with the discounted fee since she still owns the slot.
+
3532 // Alice pays 10011 drops in fees
+
3533 env(pay(alice, bob, USD(10)), path(~USD), sendmax(XRP(11)));
+
3534 BEAST_EXPECT(amm.expectBalances(
+
3535 XRPAmount{1'010'010'011},
+
3536 USD(1'000),
+
3537 IOUAmount{1'004'487'562112089, -9}));
+
3538 // Bob pays the full fee ~0.1USD
+
3539 env(pay(bob, alice, XRP(10)), path(~XRP), sendmax(USD(11)));
+
3540 if (!features[fixAMMv1_1])
+
3541 {
+
3542 BEAST_EXPECT(amm.expectBalances(
+
3543 XRPAmount{1'000'010'011},
+
3544 STAmount{USD, UINT64_C(1'010'10090898081), -11},
+
3545 IOUAmount{1'004'487'562112089, -9}));
+
3546 }
+
3547 else
+
3548 {
+
3549 BEAST_EXPECT(amm.expectBalances(
+
3550 XRPAmount{1'000'010'011},
+
3551 STAmount{USD, UINT64_C(1'010'100908980811), -12},
+
3552 IOUAmount{1'004'487'562112089, -9}));
+
3553 }
+
3554 }
+
3555
+
3556 // preflight tests
+
3557 {
+
3558 Env env(*this, features);
+
3559 auto const baseFee = env.current()->fees().base;
+
3560
+
3561 fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
+
3562 AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
+
3563 Json::Value tx = amm.bid({.account = alice, .bidMin = 500});
+
3564
+
3565 {
+
3566 auto jtx = env.jt(tx, seq(1), fee(baseFee));
+
3567 env.app().config().features.erase(featureAMM);
+
3568 PreflightContext pfctx(
+
3569 env.app(),
+
3570 *jtx.stx,
+
3571 env.current()->rules(),
+
3572 tapNONE,
+
3573 env.journal);
+
3574 auto pf = AMMBid::preflight(pfctx);
+
3575 BEAST_EXPECT(pf == temDISABLED);
+
3576 env.app().config().features.insert(featureAMM);
+
3577 }
+
3578
+
3579 {
+
3580 auto jtx = env.jt(tx, seq(1), fee(baseFee));
+
3581 jtx.jv["TxnSignature"] = "deadbeef";
+
3582 jtx.stx = env.ust(jtx);
+
3583 PreflightContext pfctx(
+
3584 env.app(),
+
3585 *jtx.stx,
+
3586 env.current()->rules(),
+
3587 tapNONE,
+
3588 env.journal);
+
3589 auto pf = AMMBid::preflight(pfctx);
+
3590 BEAST_EXPECT(pf != tesSUCCESS);
+
3591 }
+
3592
+
3593 {
+
3594 auto jtx = env.jt(tx, seq(1), fee(baseFee));
+
3595 jtx.jv["Asset2"]["currency"] = "XRP";
+
3596 jtx.jv["Asset2"].removeMember("issuer");
+
3597 jtx.stx = env.ust(jtx);
+
3598 PreflightContext pfctx(
+
3599 env.app(),
+
3600 *jtx.stx,
+
3601 env.current()->rules(),
+
3602 tapNONE,
+
3603 env.journal);
+
3604 auto pf = AMMBid::preflight(pfctx);
+
3605 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
+
3606 }
+
3607 }
+
3608 }
+
3609
+
3610 void
+
3611 testInvalidAMMPayment()
+
3612 {
+
3613 testcase("Invalid AMM Payment");
+
3614 using namespace jtx;
+
3615 using namespace std::chrono;
+
3616 using namespace std::literals::chrono_literals;
+
3617
+
3618 // Can't pay into AMM account.
+
3619 // Can't pay out since there is no keys
+
3620 for (auto const& acct : {gw, alice})
+
3621 {
+
3622 {
+
3623 Env env(*this);
+
3624 fund(env, gw, {alice, carol}, XRP(1'000), {USD(100)});
+
3625 // XRP balance is below reserve
+
3626 AMM ammAlice(env, acct, XRP(10), USD(10));
+
3627 // Pay below reserve
+
3628 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
+
3629 ter(tecNO_PERMISSION));
+
3630 // Pay above reserve
+
3631 env(pay(carol, ammAlice.ammAccount(), XRP(300)),
+
3632 ter(tecNO_PERMISSION));
+
3633 // Pay IOU
+
3634 env(pay(carol, ammAlice.ammAccount(), USD(10)),
+
3635 ter(tecNO_PERMISSION));
+
3636 }
+
3637 {
+
3638 Env env(*this);
+
3639 fund(env, gw, {alice, carol}, XRP(10'000'000), {USD(10'000)});
+
3640 // XRP balance is above reserve
+
3641 AMM ammAlice(env, acct, XRP(1'000'000), USD(100));
+
3642 // Pay below reserve
+
3643 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
+
3644 ter(tecNO_PERMISSION));
+
3645 // Pay above reserve
+
3646 env(pay(carol, ammAlice.ammAccount(), XRP(1'000'000)),
+
3647 ter(tecNO_PERMISSION));
+
3648 }
+
3649 }
+
3650
+
3651 // Can't pay into AMM with escrow.
+
3652 testAMM([&](AMM& ammAlice, Env& env) {
+
3653 auto const baseFee = env.current()->fees().base;
+
3654 env(escrow(carol, ammAlice.ammAccount(), XRP(1)),
+
3655 condition(cb1),
+
3656 finish_time(env.now() + 1s),
+
3657 cancel_time(env.now() + 2s),
+
3658 fee(baseFee * 150),
+
3659 ter(tecNO_PERMISSION));
+
3660 });
+
3661
+
3662 // Can't pay into AMM with paychan.
+
3663 testAMM([&](AMM& ammAlice, Env& env) {
+
3664 auto const pk = carol.pk();
+
3665 auto const settleDelay = 100s;
+
3666 NetClock::time_point const cancelAfter =
+
3667 env.current()->info().parentCloseTime + 200s;
+
3668 env(create(
+
3669 carol,
+
3670 ammAlice.ammAccount(),
+
3671 XRP(1'000),
+
3672 settleDelay,
+
3673 pk,
+
3674 cancelAfter),
+
3675 ter(tecNO_PERMISSION));
+
3676 });
+
3677
+
3678 // Can't pay into AMM with checks.
+
3679 testAMM([&](AMM& ammAlice, Env& env) {
+
3680 env(check::create(env.master.id(), ammAlice.ammAccount(), XRP(100)),
+
3681 ter(tecNO_PERMISSION));
+
3682 });
+
3683
+
3684 // Pay amounts close to one side of the pool
+
3685 testAMM(
+
3686 [&](AMM& ammAlice, Env& env) {
+
3687 // Can't consume whole pool
+
3688 env(pay(alice, carol, USD(100)),
+
3689 path(~USD),
+
3690 sendmax(XRP(1'000'000'000)),
+
3691 ter(tecPATH_PARTIAL));
+
3692 env(pay(alice, carol, XRP(100)),
+
3693 path(~XRP),
+
3694 sendmax(USD(1'000'000'000)),
+
3695 ter(tecPATH_PARTIAL));
+
3696 // Overflow
+
3697 env(pay(alice,
+
3698 carol,
+
3699 STAmount{USD, UINT64_C(99'999999999), -9}),
+
3700 path(~USD),
+
3701 sendmax(XRP(1'000'000'000)),
+
3702 ter(tecPATH_PARTIAL));
+
3703 env(pay(alice,
+
3704 carol,
+
3705 STAmount{USD, UINT64_C(999'99999999), -8}),
+
3706 path(~USD),
+
3707 sendmax(XRP(1'000'000'000)),
+
3708 ter(tecPATH_PARTIAL));
+
3709 env(pay(alice, carol, STAmount{xrpIssue(), 99'999'999}),
+
3710 path(~XRP),
+
3711 sendmax(USD(1'000'000'000)),
+
3712 ter(tecPATH_PARTIAL));
+
3713 // Sender doesn't have enough funds
+
3714 env(pay(alice, carol, USD(99.99)),
+
3715 path(~USD),
+
3716 sendmax(XRP(1'000'000'000)),
+
3717 ter(tecPATH_PARTIAL));
+
3718 env(pay(alice, carol, STAmount{xrpIssue(), 99'990'000}),
+
3719 path(~XRP),
+
3720 sendmax(USD(1'000'000'000)),
+
3721 ter(tecPATH_PARTIAL));
+
3722 },
+
3723 {{XRP(100), USD(100)}});
+
3724
+
3725 // Globally frozen
+
3726 testAMM([&](AMM& ammAlice, Env& env) {
+
3727 env(fset(gw, asfGlobalFreeze));
+
3728 env.close();
+
3729 env(pay(alice, carol, USD(1)),
+
3730 path(~USD),
+
3731 txflags(tfPartialPayment | tfNoRippleDirect),
+
3732 sendmax(XRP(10)),
+
3733 ter(tecPATH_DRY));
+
3734 env(pay(alice, carol, XRP(1)),
+
3735 path(~XRP),
+
3736 txflags(tfPartialPayment | tfNoRippleDirect),
+
3737 sendmax(USD(10)),
+
3738 ter(tecPATH_DRY));
+
3739 });
3740
-
3741 BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}}));
-
3742 }
-
3743 // Initial 30,000 + 100
-
3744 BEAST_EXPECT(expectLine(env, carol, STAmount{USD, 30'100}));
-
3745 // Initial 1,000 - 30082730(AMM pool) - 70798251(offer) - 10(tx fee)
-
3746 BEAST_EXPECT(expectLedgerEntryRoot(
-
3747 env,
-
3748 bob,
-
3749 XRP(1'000) - XRPAmount{30'082'730} - XRPAmount{70'798'251} -
-
3750 txfee(env, 1)));
-
3751 }
-
3752
-
3753 // Default path (with AMM) has a better quality than a non-default path.
-
3754 // The max possible liquidity is taken out of default
-
3755 // path ~49XRP/49USD. The rest is taken from the offer.
-
3756 testAMM(
-
3757 [&](AMM& ammAlice, Env& env) {
-
3758 env.fund(XRP(1'000), bob);
-
3759 env.close();
-
3760 env.trust(EUR(2'000), alice);
-
3761 env.close();
-
3762 env(pay(gw, alice, EUR(1'000)));
-
3763 env(offer(alice, XRP(101), EUR(100)), txflags(tfPassive));
-
3764 env.close();
-
3765 env(offer(alice, EUR(100), USD(100)), txflags(tfPassive));
-
3766 env.close();
-
3767 env(pay(bob, carol, USD(100)),
-
3768 path(~EUR, ~USD),
-
3769 sendmax(XRP(102)),
-
3770 txflags(tfPartialPayment));
-
3771 env.close();
-
3772 BEAST_EXPECT(ammAlice.expectBalances(
-
3773 XRPAmount(10'050'238'637),
-
3774 STAmount(USD, UINT64_C(9'950'01249687578), -11),
-
3775 ammAlice.tokens()));
-
3776 BEAST_EXPECT(expectOffers(
-
3777 env,
-
3778 alice,
-
3779 2,
-
3780 {{Amounts{
-
3781 XRPAmount(50'487'378),
-
3782 STAmount(EUR, UINT64_C(49'98750312422), -11)},
-
3783 Amounts{
-
3784 STAmount(EUR, UINT64_C(49'98750312422), -11),
-
3785 STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
-
3786 // Initial 30,000 + 99.99999999999
-
3787 BEAST_EXPECT(expectLine(
-
3788 env,
-
3789 carol,
-
3790 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
-
3791 // Initial 1,000 - 50238637(AMM pool) - 50512622(offer) - 10(tx
-
3792 // fee)
-
3793 BEAST_EXPECT(expectLedgerEntryRoot(
-
3794 env,
-
3795 bob,
-
3796 XRP(1'000) - XRPAmount{50'238'637} - XRPAmount{50'512'622} -
-
3797 txfee(env, 1)));
-
3798 },
-
3799 std::nullopt,
-
3800 0,
-
3801 std::nullopt,
-
3802 {features});
-
3803
-
3804 // Default path with AMM and Order Book offer. AMM is consumed first,
-
3805 // remaining amount is consumed by the offer.
-
3806 testAMM(
-
3807 [&](AMM& ammAlice, Env& env) {
-
3808 fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
+
3741 // Individually frozen AMM
+
3742 testAMM([&](AMM& ammAlice, Env& env) {
+
3743 env(trust(
+
3744 gw,
+
3745 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
+
3746 tfSetFreeze));
+
3747 env.close();
+
3748 env(pay(alice, carol, USD(1)),
+
3749 path(~USD),
+
3750 txflags(tfPartialPayment | tfNoRippleDirect),
+
3751 sendmax(XRP(10)),
+
3752 ter(tecPATH_DRY));
+
3753 env(pay(alice, carol, XRP(1)),
+
3754 path(~XRP),
+
3755 txflags(tfPartialPayment | tfNoRippleDirect),
+
3756 sendmax(USD(10)),
+
3757 ter(tecPATH_DRY));
+
3758 });
+
3759
+
3760 // Individually frozen accounts
+
3761 testAMM([&](AMM& ammAlice, Env& env) {
+
3762 env(trust(gw, carol["USD"](0), tfSetFreeze));
+
3763 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
3764 env.close();
+
3765 env(pay(alice, carol, XRP(1)),
+
3766 path(~XRP),
+
3767 sendmax(USD(10)),
+
3768 txflags(tfNoRippleDirect | tfPartialPayment),
+
3769 ter(tecPATH_DRY));
+
3770 });
+
3771 }
+
3772
+
3773 void
+
3774 testBasicPaymentEngine(FeatureBitset features)
+
3775 {
+
3776 testcase("Basic Payment");
+
3777 using namespace jtx;
+
3778
+
3779 // Payment 100USD for 100XRP.
+
3780 // Force one path with tfNoRippleDirect.
+
3781 testAMM(
+
3782 [&](AMM& ammAlice, Env& env) {
+
3783 env.fund(jtx::XRP(30'000), bob);
+
3784 env.close();
+
3785 env(pay(bob, carol, USD(100)),
+
3786 path(~USD),
+
3787 sendmax(XRP(100)),
+
3788 txflags(tfNoRippleDirect));
+
3789 env.close();
+
3790 BEAST_EXPECT(ammAlice.expectBalances(
+
3791 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
3792 // Initial balance 30,000 + 100
+
3793 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
3794 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
+
3795 BEAST_EXPECT(expectLedgerEntryRoot(
+
3796 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
+
3797 },
+
3798 {{XRP(10'000), USD(10'100)}},
+
3799 0,
+
3800 std::nullopt,
+
3801 {features});
+
3802
+
3803 // Payment 100USD for 100XRP, use default path.
+
3804 testAMM(
+
3805 [&](AMM& ammAlice, Env& env) {
+
3806 env.fund(jtx::XRP(30'000), bob);
+
3807 env.close();
+
3808 env(pay(bob, carol, USD(100)), sendmax(XRP(100)));
3809 env.close();
-
3810 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
-
3811 env.close();
-
3812 env(pay(alice, carol, USD(200)),
-
3813 sendmax(XRP(200)),
-
3814 txflags(tfPartialPayment));
-
3815 env.close();
-
3816 if (!features[fixAMMv1_1])
-
3817 {
-
3818 BEAST_EXPECT(ammAlice.expectBalances(
-
3819 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3820 // Initial 30,000 + 200
-
3821 BEAST_EXPECT(expectLine(env, carol, USD(30'200)));
-
3822 }
-
3823 else
-
3824 {
-
3825 BEAST_EXPECT(ammAlice.expectBalances(
-
3826 XRP(10'100),
-
3827 STAmount(USD, UINT64_C(10'000'00000000001), -11),
-
3828 ammAlice.tokens()));
-
3829 BEAST_EXPECT(expectLine(
-
3830 env,
-
3831 carol,
-
3832 STAmount(USD, UINT64_C(30'199'99999999999), -11)));
-
3833 }
-
3834 // Initial 30,000 - 10000(AMM pool LP) - 100(AMM offer) -
-
3835 // - 100(offer) - 10(tx fee) - one reserve
+
3810 BEAST_EXPECT(ammAlice.expectBalances(
+
3811 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
3812 // Initial balance 30,000 + 100
+
3813 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
3814 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
+
3815 BEAST_EXPECT(expectLedgerEntryRoot(
+
3816 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
+
3817 },
+
3818 {{XRP(10'000), USD(10'100)}},
+
3819 0,
+
3820 std::nullopt,
+
3821 {features});
+
3822
+
3823 // This payment is identical to above. While it has
+
3824 // both default path and path, activeStrands has one path.
+
3825 testAMM(
+
3826 [&](AMM& ammAlice, Env& env) {
+
3827 env.fund(jtx::XRP(30'000), bob);
+
3828 env.close();
+
3829 env(pay(bob, carol, USD(100)), path(~USD), sendmax(XRP(100)));
+
3830 env.close();
+
3831 BEAST_EXPECT(ammAlice.expectBalances(
+
3832 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
3833 // Initial balance 30,000 + 100
+
3834 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
3835 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
3836 BEAST_EXPECT(expectLedgerEntryRoot(
-
3837 env,
-
3838 alice,
-
3839 XRP(30'000) - XRP(10'000) - XRP(100) - XRP(100) -
-
3840 ammCrtFee(env) - txfee(env, 1)));
-
3841 BEAST_EXPECT(expectOffers(env, bob, 0));
-
3842 },
-
3843 {{XRP(10'000), USD(10'100)}},
-
3844 0,
-
3845 std::nullopt,
-
3846 {features});
-
3847
-
3848 // Default path with AMM and Order Book offer.
-
3849 // Order Book offer is consumed first.
-
3850 // Remaining amount is consumed by AMM.
-
3851 {
-
3852 Env env(*this, features);
-
3853 fund(env, gw, {alice, bob, carol}, XRP(20'000), {USD(2'000)});
-
3854 env.close();
-
3855 env(offer(bob, XRP(50), USD(150)), txflags(tfPassive));
-
3856 env.close();
-
3857 AMM ammAlice(env, alice, XRP(1'000), USD(1'050));
-
3858 env(pay(alice, carol, USD(200)),
-
3859 sendmax(XRP(200)),
-
3860 txflags(tfPartialPayment));
-
3861 env.close();
-
3862 BEAST_EXPECT(ammAlice.expectBalances(
-
3863 XRP(1'050), USD(1'000), ammAlice.tokens()));
-
3864 BEAST_EXPECT(expectLine(env, carol, USD(2'200)));
-
3865 BEAST_EXPECT(expectOffers(env, bob, 0));
-
3866 }
-
3867
-
3868 // Offer crossing XRP/IOU
-
3869 testAMM(
-
3870 [&](AMM& ammAlice, Env& env) {
-
3871 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
-
3872 env.close();
-
3873 env(offer(bob, USD(100), XRP(100)));
+
3837 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
+
3838 },
+
3839 {{XRP(10'000), USD(10'100)}},
+
3840 0,
+
3841 std::nullopt,
+
3842 {features});
+
3843
+
3844 // Payment with limitQuality set.
+
3845 testAMM(
+
3846 [&](AMM& ammAlice, Env& env) {
+
3847 env.fund(jtx::XRP(30'000), bob);
+
3848 env.close();
+
3849 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
+
3850 // would have been sent has it not been for limitQuality.
+
3851 env(pay(bob, carol, USD(100)),
+
3852 path(~USD),
+
3853 sendmax(XRP(100)),
+
3854 txflags(
+
3855 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
3856 env.close();
+
3857 BEAST_EXPECT(ammAlice.expectBalances(
+
3858 XRP(10'010), USD(10'000), ammAlice.tokens()));
+
3859 // Initial balance 30,000 + 10(limited by limitQuality)
+
3860 BEAST_EXPECT(expectLine(env, carol, USD(30'010)));
+
3861 // Initial balance 30,000 - 10(limited by limitQuality) - 10(tx
+
3862 // fee)
+
3863 BEAST_EXPECT(expectLedgerEntryRoot(
+
3864 env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
+
3865
+
3866 // Fails because of limitQuality. Would have sent
+
3867 // ~98.91USD/110XRP has it not been for limitQuality.
+
3868 env(pay(bob, carol, USD(100)),
+
3869 path(~USD),
+
3870 sendmax(XRP(100)),
+
3871 txflags(
+
3872 tfNoRippleDirect | tfPartialPayment | tfLimitQuality),
+
3873 ter(tecPATH_DRY));
3874 env.close();
-
3875 BEAST_EXPECT(ammAlice.expectBalances(
-
3876 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3877 // Initial 1,000 + 100
-
3878 BEAST_EXPECT(expectLine(env, bob, USD(1'100)));
-
3879 // Initial 30,000 - 100(offer) - 10(tx fee)
-
3880 BEAST_EXPECT(expectLedgerEntryRoot(
-
3881 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
-
3882 BEAST_EXPECT(expectOffers(env, bob, 0));
-
3883 },
-
3884 {{XRP(10'000), USD(10'100)}},
-
3885 0,
-
3886 std::nullopt,
-
3887 {features});
-
3888
-
3889 // Offer crossing IOU/IOU and transfer rate
-
3890 // Single path AMM offer
-
3891 testAMM(
-
3892 [&](AMM& ammAlice, Env& env) {
-
3893 env(rate(gw, 1.25));
-
3894 env.close();
-
3895 // This offer succeeds to cross pre- and post-amendment
-
3896 // because the strand's out amount is small enough to match
-
3897 // limitQuality value and limitOut() function in StrandFlow
-
3898 // doesn't require an adjustment to out value.
-
3899 env(offer(carol, EUR(100), GBP(100)));
-
3900 env.close();
-
3901 // No transfer fee
-
3902 BEAST_EXPECT(ammAlice.expectBalances(
-
3903 GBP(1'100), EUR(1'000), ammAlice.tokens()));
-
3904 // Initial 30,000 - 100(offer) - 25% transfer fee
-
3905 BEAST_EXPECT(expectLine(env, carol, GBP(29'875)));
-
3906 // Initial 30,000 + 100(offer)
-
3907 BEAST_EXPECT(expectLine(env, carol, EUR(30'100)));
-
3908 BEAST_EXPECT(expectOffers(env, bob, 0));
-
3909 },
-
3910 {{GBP(1'000), EUR(1'100)}},
-
3911 0,
-
3912 std::nullopt,
-
3913 {features});
-
3914 // Single-path AMM offer
-
3915 testAMM(
-
3916 [&](AMM& amm, Env& env) {
-
3917 env(rate(gw, 1.001));
-
3918 env.close();
-
3919 env(offer(carol, XRP(100), USD(55)));
-
3920 env.close();
-
3921 if (!features[fixAMMv1_1])
-
3922 {
-
3923 // Pre-amendment the transfer fee is not taken into
-
3924 // account when calculating the limit out based on
-
3925 // limitQuality. Carol pays 0.1% on the takerGets, which
-
3926 // lowers the overall quality. AMM offer is generated based
-
3927 // on higher limit out, which generates a larger offer
-
3928 // with lower quality. Consequently, the offer fails
-
3929 // to cross.
-
3930 BEAST_EXPECT(
-
3931 amm.expectBalances(XRP(1'000), USD(500), amm.tokens()));
-
3932 BEAST_EXPECT(expectOffers(
-
3933 env, carol, 1, {{Amounts{XRP(100), USD(55)}}}));
-
3934 }
-
3935 else
-
3936 {
-
3937 // Post-amendment the transfer fee is taken into account
-
3938 // when calculating the limit out based on limitQuality.
-
3939 // This increases the limitQuality and decreases
-
3940 // the limit out. Consequently, AMM offer size is decreased,
-
3941 // and the quality is increased, matching the overall
-
3942 // quality.
-
3943 // AMM offer ~50USD/91XRP
-
3944 BEAST_EXPECT(amm.expectBalances(
-
3945 XRPAmount(909'090'909),
-
3946 STAmount{USD, UINT64_C(550'000000055), -9},
-
3947 amm.tokens()));
-
3948 // Offer ~91XRP/49.99USD
-
3949 BEAST_EXPECT(expectOffers(
-
3950 env,
-
3951 carol,
-
3952 1,
-
3953 {{Amounts{
-
3954 XRPAmount{9'090'909},
-
3955 STAmount{USD, 4'99999995, -8}}}}));
-
3956 // Carol pays 0.1% fee on ~50USD =~ 0.05USD
-
3957 BEAST_EXPECT(
-
3958 env.balance(carol, USD) ==
-
3959 STAmount(USD, UINT64_C(29'949'94999999494), -11));
-
3960 }
-
3961 },
-
3962 {{XRP(1'000), USD(500)}},
-
3963 0,
-
3964 std::nullopt,
-
3965 {features});
-
3966 testAMM(
-
3967 [&](AMM& amm, Env& env) {
-
3968 env(rate(gw, 1.001));
-
3969 env.close();
-
3970 env(offer(carol, XRP(10), USD(5.5)));
-
3971 env.close();
-
3972 if (!features[fixAMMv1_1])
-
3973 {
-
3974 BEAST_EXPECT(amm.expectBalances(
-
3975 XRP(990),
-
3976 STAmount{USD, UINT64_C(505'050505050505), -12},
-
3977 amm.tokens()));
-
3978 BEAST_EXPECT(expectOffers(env, carol, 0));
-
3979 }
-
3980 else
-
3981 {
-
3982 BEAST_EXPECT(amm.expectBalances(
-
3983 XRP(990),
-
3984 STAmount{USD, UINT64_C(505'0505050505051), -13},
-
3985 amm.tokens()));
-
3986 BEAST_EXPECT(expectOffers(env, carol, 0));
-
3987 }
-
3988 },
-
3989 {{XRP(1'000), USD(500)}},
-
3990 0,
-
3991 std::nullopt,
-
3992 {features});
-
3993 // Multi-path AMM offer
-
3994 testAMM(
-
3995 [&](AMM& ammAlice, Env& env) {
-
3996 Account const ed("ed");
-
3997 fund(
-
3998 env,
-
3999 gw,
-
4000 {bob, ed},
-
4001 XRP(30'000),
-
4002 {GBP(2'000), EUR(2'000)},
-
4003 Fund::Acct);
-
4004 env(rate(gw, 1.25));
-
4005 env.close();
-
4006 // The auto-bridge is worse quality than AMM, is not consumed
-
4007 // first and initially forces multi-path AMM offer generation.
-
4008 // Multi-path AMM offers are consumed until their quality
-
4009 // is less than the auto-bridge offers quality. Auto-bridge
-
4010 // offers are consumed afterward. Then the behavior is
-
4011 // different pre-amendment and post-amendment.
-
4012 env(offer(bob, GBP(10), XRP(10)), txflags(tfPassive));
-
4013 env(offer(ed, XRP(10), EUR(10)), txflags(tfPassive));
-
4014 env.close();
-
4015 env(offer(carol, EUR(100), GBP(100)));
-
4016 env.close();
-
4017 if (!features[fixAMMv1_1])
-
4018 {
-
4019 // After the auto-bridge offers are consumed, single path
-
4020 // AMM offer is generated with the limit out not taking
-
4021 // into consideration the transfer fee. This results
-
4022 // in an overall lower quality offer than the limit quality
-
4023 // and the single path AMM offer fails to consume.
-
4024 // Total consumed ~37.06GBP/39.32EUR
-
4025 BEAST_EXPECT(ammAlice.expectBalances(
-
4026 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
-
4027 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
-
4028 ammAlice.tokens()));
-
4029 // Consumed offer ~49.32EUR/49.32GBP
-
4030 BEAST_EXPECT(expectOffers(
-
4031 env,
-
4032 carol,
-
4033 1,
-
4034 {Amounts{
-
4035 STAmount{EUR, UINT64_C(50'684828792831), -12},
-
4036 STAmount{GBP, UINT64_C(50'684828792831), -12}}}));
-
4037 BEAST_EXPECT(expectOffers(env, bob, 0));
-
4038 BEAST_EXPECT(expectOffers(env, ed, 0));
-
4039
-
4040 // Initial 30,000 - ~47.06(offers = 37.06(AMM) + 10(LOB))
-
4041 // * 1.25
-
4042 // = 58.825 = ~29941.17
-
4043 // carol bought ~72.93EUR at the cost of ~70.68GBP
-
4044 // the offer is partially consumed
-
4045 BEAST_EXPECT(expectLine(
-
4046 env,
-
4047 carol,
-
4048 STAmount{GBP, UINT64_C(29'941'16770347333), -11}));
-
4049 // Initial 30,000 + ~49.3(offers = 39.3(AMM) + 10(LOB))
-
4050 BEAST_EXPECT(expectLine(
-
4051 env,
-
4052 carol,
-
4053 STAmount{EUR, UINT64_C(30'049'31517120716), -11}));
-
4054 }
-
4055 else
-
4056 {
-
4057 // After the auto-bridge offers are consumed, single path
-
4058 // AMM offer is generated with the limit out taking
-
4059 // into consideration the transfer fee. This results
-
4060 // in an overall quality offer matching the limit quality
-
4061 // and the single path AMM offer is consumed. More
-
4062 // liquidity is consumed overall in post-amendment.
-
4063 // Total consumed ~60.68GBP/62.93EUR
-
4064 BEAST_EXPECT(ammAlice.expectBalances(
-
4065 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
-
4066 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
-
4067 ammAlice.tokens()));
-
4068 // Consumed offer ~72.93EUR/72.93GBP
-
4069 BEAST_EXPECT(expectOffers(
-
4070 env,
-
4071 carol,
-
4072 1,
-
4073 {Amounts{
-
4074 STAmount{EUR, UINT64_C(27'06583722134028), -14},
-
4075 STAmount{GBP, UINT64_C(27'06583722134028), -14}}}));
-
4076 BEAST_EXPECT(expectOffers(env, bob, 0));
-
4077 BEAST_EXPECT(expectOffers(env, ed, 0));
-
4078
-
4079 // Initial 30,000 - ~70.68(offers = 60.68(AMM) + 10(LOB))
-
4080 // * 1.25
-
4081 // = 88.35 = ~29911.64
-
4082 // carol bought ~72.93EUR at the cost of ~70.68GBP
-
4083 // the offer is partially consumed
-
4084 BEAST_EXPECT(expectLine(
-
4085 env,
-
4086 carol,
-
4087 STAmount{GBP, UINT64_C(29'911'64396400896), -11}));
-
4088 // Initial 30,000 + ~72.93(offers = 62.93(AMM) + 10(LOB))
-
4089 BEAST_EXPECT(expectLine(
-
4090 env,
-
4091 carol,
-
4092 STAmount{EUR, UINT64_C(30'072'93416277865), -11}));
-
4093 }
-
4094 // Initial 2000 + 10 = 2010
-
4095 BEAST_EXPECT(expectLine(env, bob, GBP(2'010)));
-
4096 // Initial 2000 - 10 * 1.25 = 1987.5
-
4097 BEAST_EXPECT(expectLine(env, ed, EUR(1'987.5)));
-
4098 },
-
4099 {{GBP(1'000), EUR(1'100)}},
-
4100 0,
-
4101 std::nullopt,
-
4102 {features});
-
4103
-
4104 // Payment and transfer fee
-
4105 // Scenario:
-
4106 // Bob sends 125GBP to pay 80EUR to Carol
-
4107 // Payment execution:
-
4108 // bob's 125GBP/1.25 = 100GBP
-
4109 // 100GBP/100EUR AMM offer
-
4110 // 100EUR/1.25 = 80EUR paid to carol
-
4111 testAMM(
-
4112 [&](AMM& ammAlice, Env& env) {
-
4113 fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
-
4114 env(rate(gw, 1.25));
-
4115 env.close();
-
4116 env(pay(bob, carol, EUR(100)),
-
4117 path(~EUR),
-
4118 sendmax(GBP(125)),
-
4119 txflags(tfPartialPayment));
-
4120 env.close();
-
4121 BEAST_EXPECT(ammAlice.expectBalances(
-
4122 GBP(1'100), EUR(1'000), ammAlice.tokens()));
-
4123 BEAST_EXPECT(expectLine(env, bob, GBP(75)));
-
4124 BEAST_EXPECT(expectLine(env, carol, EUR(30'080)));
-
4125 },
-
4126 {{GBP(1'000), EUR(1'100)}},
-
4127 0,
-
4128 std::nullopt,
-
4129 {features});
-
4130
-
4131 // Payment and transfer fee, multiple steps
-
4132 // Scenario:
-
4133 // Dan's offer 200CAN/200GBP
-
4134 // AMM 1000GBP/10125EUR
-
4135 // Ed's offer 200EUR/200USD
-
4136 // Bob sends 195.3125CAN to pay 100USD to Carol
-
4137 // Payment execution:
-
4138 // bob's 195.3125CAN/1.25 = 156.25CAN -> dan's offer
-
4139 // 156.25CAN/156.25GBP 156.25GBP/1.25 = 125GBP -> AMM's offer
-
4140 // 125GBP/125EUR 125EUR/1.25 = 100EUR -> ed's offer
-
4141 // 100EUR/100USD 100USD/1.25 = 80USD paid to carol
-
4142 testAMM(
-
4143 [&](AMM& ammAlice, Env& env) {
-
4144 Account const dan("dan");
-
4145 Account const ed("ed");
-
4146 auto const CAN = gw["CAN"];
-
4147 fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
-
4148 fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
-
4149 fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
-
4150 env(trust(carol, USD(100)));
-
4151 env(rate(gw, 1.25));
-
4152 env.close();
-
4153 env(offer(dan, CAN(200), GBP(200)));
-
4154 env(offer(ed, EUR(200), USD(200)));
-
4155 env.close();
-
4156 env(pay(bob, carol, USD(100)),
-
4157 path(~GBP, ~EUR, ~USD),
-
4158 sendmax(CAN(195.3125)),
-
4159 txflags(tfPartialPayment));
-
4160 env.close();
-
4161 BEAST_EXPECT(expectLine(env, bob, CAN(0)));
-
4162 BEAST_EXPECT(expectLine(env, dan, CAN(356.25), GBP(43.75)));
-
4163 BEAST_EXPECT(ammAlice.expectBalances(
-
4164 GBP(10'125), EUR(10'000), ammAlice.tokens()));
-
4165 BEAST_EXPECT(expectLine(env, ed, EUR(300), USD(100)));
-
4166 BEAST_EXPECT(expectLine(env, carol, USD(80)));
-
4167 },
-
4168 {{GBP(10'000), EUR(10'125)}},
-
4169 0,
-
4170 std::nullopt,
-
4171 {features});
-
4172
-
4173 // Pay amounts close to one side of the pool
-
4174 testAMM(
-
4175 [&](AMM& ammAlice, Env& env) {
-
4176 env(pay(alice, carol, USD(99.99)),
-
4177 path(~USD),
-
4178 sendmax(XRP(1)),
-
4179 txflags(tfPartialPayment),
-
4180 ter(tesSUCCESS));
-
4181 env(pay(alice, carol, USD(100)),
-
4182 path(~USD),
-
4183 sendmax(XRP(1)),
-
4184 txflags(tfPartialPayment),
-
4185 ter(tesSUCCESS));
-
4186 env(pay(alice, carol, XRP(100)),
-
4187 path(~XRP),
-
4188 sendmax(USD(1)),
-
4189 txflags(tfPartialPayment),
-
4190 ter(tesSUCCESS));
-
4191 env(pay(alice, carol, STAmount{xrpIssue(), 99'999'900}),
-
4192 path(~XRP),
-
4193 sendmax(USD(1)),
-
4194 txflags(tfPartialPayment),
-
4195 ter(tesSUCCESS));
-
4196 },
-
4197 {{XRP(100), USD(100)}},
-
4198 0,
-
4199 std::nullopt,
-
4200 {features});
-
4201
-
4202 // Multiple paths/steps
-
4203 {
-
4204 Env env(*this, features);
-
4205 auto const ETH = gw["ETH"];
-
4206 fund(
-
4207 env,
-
4208 gw,
-
4209 {alice},
-
4210 XRP(100'000),
-
4211 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
-
4212 fund(env, gw, {carol, bob}, XRP(1'000), {USD(200)}, Fund::Acct);
-
4213 AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
-
4214 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
-
4215 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
-
4216 AMM xrp_usd(env, alice, XRP(10'150), USD(10'200));
-
4217 AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
-
4218 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
-
4219 AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
-
4220 env(pay(bob, carol, USD(100)),
-
4221 path(~EUR, ~BTC, ~USD),
-
4222 path(~USD),
-
4223 path(~ETH, ~EUR, ~USD),
-
4224 sendmax(XRP(200)));
-
4225 if (!features[fixAMMv1_1])
-
4226 {
-
4227 // XRP-ETH-EUR-USD
-
4228 // This path provides ~26.06USD/26.2XRP
-
4229 BEAST_EXPECT(xrp_eth.expectBalances(
-
4230 XRPAmount(10'026'208'900),
-
4231 STAmount{ETH, UINT64_C(10'073'65779244494), -11},
-
4232 xrp_eth.tokens()));
-
4233 BEAST_EXPECT(eth_eur.expectBalances(
-
4234 STAmount{ETH, UINT64_C(10'926'34220755506), -11},
-
4235 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
-
4236 eth_eur.tokens()));
-
4237 BEAST_EXPECT(eur_usd.expectBalances(
-
4238 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
-
4239 STAmount{USD, UINT64_C(9'973'93151712086), -11},
-
4240 eur_usd.tokens()));
-
4241 // XRP-USD path
-
4242 // This path provides ~73.9USD/74.1XRP
-
4243 BEAST_EXPECT(xrp_usd.expectBalances(
-
4244 XRPAmount(10'224'106'246),
-
4245 STAmount{USD, UINT64_C(10'126'06848287914), -11},
-
4246 xrp_usd.tokens()));
-
4247 }
-
4248 else
-
4249 {
-
4250 BEAST_EXPECT(xrp_eth.expectBalances(
-
4251 XRPAmount(10'026'208'900),
-
4252 STAmount{ETH, UINT64_C(10'073'65779244461), -11},
-
4253 xrp_eth.tokens()));
-
4254 BEAST_EXPECT(eth_eur.expectBalances(
-
4255 STAmount{ETH, UINT64_C(10'926'34220755539), -11},
-
4256 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
-
4257 eth_eur.tokens()));
-
4258 BEAST_EXPECT(eur_usd.expectBalances(
-
4259 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
-
4260 STAmount{USD, UINT64_C(9'973'93151712057), -11},
-
4261 eur_usd.tokens()));
-
4262 // XRP-USD path
-
4263 // This path provides ~73.9USD/74.1XRP
-
4264 BEAST_EXPECT(xrp_usd.expectBalances(
-
4265 XRPAmount(10'224'106'246),
-
4266 STAmount{USD, UINT64_C(10'126'06848287943), -11},
-
4267 xrp_usd.tokens()));
-
4268 }
-
4269
-
4270 // XRP-EUR-BTC-USD
-
4271 // This path doesn't provide any liquidity due to how
-
4272 // offers are generated in multi-path. Analytical solution
-
4273 // shows a different distribution:
-
4274 // XRP-EUR-BTC-USD 11.6USD/11.64XRP, XRP-USD 60.7USD/60.8XRP,
-
4275 // XRP-ETH-EUR-USD 27.6USD/27.6XRP
-
4276 BEAST_EXPECT(xrp_eur.expectBalances(
-
4277 XRP(10'100), EUR(10'000), xrp_eur.tokens()));
-
4278 BEAST_EXPECT(eur_btc.expectBalances(
-
4279 EUR(10'000), BTC(10'200), eur_btc.tokens()));
-
4280 BEAST_EXPECT(btc_usd.expectBalances(
-
4281 BTC(10'100), USD(10'000), btc_usd.tokens()));
+
3875 },
+
3876 {{XRP(10'000), USD(10'010)}},
+
3877 0,
+
3878 std::nullopt,
+
3879 {features});
+
3880
+
3881 // Payment with limitQuality and transfer fee set.
+
3882 testAMM(
+
3883 [&](AMM& ammAlice, Env& env) {
+
3884 env(rate(gw, 1.1));
+
3885 env.close();
+
3886 env.fund(jtx::XRP(30'000), bob);
+
3887 env.close();
+
3888 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
+
3889 // would have been sent has it not been for limitQuality and
+
3890 // the transfer fee.
+
3891 env(pay(bob, carol, USD(100)),
+
3892 path(~USD),
+
3893 sendmax(XRP(110)),
+
3894 txflags(
+
3895 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
3896 env.close();
+
3897 BEAST_EXPECT(ammAlice.expectBalances(
+
3898 XRP(10'010), USD(10'000), ammAlice.tokens()));
+
3899 // 10USD - 10% transfer fee
+
3900 BEAST_EXPECT(expectLine(
+
3901 env,
+
3902 carol,
+
3903 STAmount{USD, UINT64_C(30'009'09090909091), -11}));
+
3904 BEAST_EXPECT(expectLedgerEntryRoot(
+
3905 env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
+
3906 },
+
3907 {{XRP(10'000), USD(10'010)}},
+
3908 0,
+
3909 std::nullopt,
+
3910 {features});
+
3911
+
3912 // Fail when partial payment is not set.
+
3913 testAMM(
+
3914 [&](AMM& ammAlice, Env& env) {
+
3915 env.fund(jtx::XRP(30'000), bob);
+
3916 env.close();
+
3917 env(pay(bob, carol, USD(100)),
+
3918 path(~USD),
+
3919 sendmax(XRP(100)),
+
3920 txflags(tfNoRippleDirect),
+
3921 ter(tecPATH_PARTIAL));
+
3922 },
+
3923 {{XRP(10'000), USD(10'000)}},
+
3924 0,
+
3925 std::nullopt,
+
3926 {features});
+
3927
+
3928 // Non-default path (with AMM) has a better quality than default path.
+
3929 // The max possible liquidity is taken out of non-default
+
3930 // path ~29.9XRP/29.9EUR, ~29.9EUR/~29.99USD. The rest
+
3931 // is taken from the offer.
+
3932 {
+
3933 Env env(*this, features);
+
3934 fund(
+
3935 env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
+
3936 env.close();
+
3937 env.fund(XRP(1'000), bob);
+
3938 env.close();
+
3939 auto ammEUR_XRP = AMM(env, alice, XRP(10'000), EUR(10'000));
+
3940 auto ammUSD_EUR = AMM(env, alice, EUR(10'000), USD(10'000));
+
3941 env(offer(alice, XRP(101), USD(100)), txflags(tfPassive));
+
3942 env.close();
+
3943 env(pay(bob, carol, USD(100)),
+
3944 path(~EUR, ~USD),
+
3945 sendmax(XRP(102)),
+
3946 txflags(tfPartialPayment));
+
3947 env.close();
+
3948 BEAST_EXPECT(ammEUR_XRP.expectBalances(
+
3949 XRPAmount(10'030'082'730),
+
3950 STAmount(EUR, UINT64_C(9'970'007498125468), -12),
+
3951 ammEUR_XRP.tokens()));
+
3952 if (!features[fixAMMv1_1])
+
3953 {
+
3954 BEAST_EXPECT(ammUSD_EUR.expectBalances(
+
3955 STAmount(USD, UINT64_C(9'970'097277662122), -12),
+
3956 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
+
3957 ammUSD_EUR.tokens()));
+
3958
+
3959 // fixReducedOffersV2 changes the expected results slightly.
+
3960 Amounts const expectedAmounts =
+
3961 env.closed()->rules().enabled(fixReducedOffersV2)
+
3962 ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233787816), -14)}
+
3963 : Amounts{
+
3964 XRPAmount(30'201'749),
+
3965 STAmount(USD, UINT64_C(29'90272233787818), -14)};
+
3966
+
3967 BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}}));
+
3968 }
+
3969 else
+
3970 {
+
3971 BEAST_EXPECT(ammUSD_EUR.expectBalances(
+
3972 STAmount(USD, UINT64_C(9'970'097277662172), -12),
+
3973 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
+
3974 ammUSD_EUR.tokens()));
+
3975
+
3976 // fixReducedOffersV2 changes the expected results slightly.
+
3977 Amounts const expectedAmounts =
+
3978 env.closed()->rules().enabled(fixReducedOffersV2)
+
3979 ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233782839), -14)}
+
3980 : Amounts{
+
3981 XRPAmount(30'201'749),
+
3982 STAmount(USD, UINT64_C(29'90272233782840), -14)};
+
3983
+
3984 BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}}));
+
3985 }
+
3986 // Initial 30,000 + 100
+
3987 BEAST_EXPECT(expectLine(env, carol, STAmount{USD, 30'100}));
+
3988 // Initial 1,000 - 30082730(AMM pool) - 70798251(offer) - 10(tx fee)
+
3989 BEAST_EXPECT(expectLedgerEntryRoot(
+
3990 env,
+
3991 bob,
+
3992 XRP(1'000) - XRPAmount{30'082'730} - XRPAmount{70'798'251} -
+
3993 txfee(env, 1)));
+
3994 }
+
3995
+
3996 // Default path (with AMM) has a better quality than a non-default path.
+
3997 // The max possible liquidity is taken out of default
+
3998 // path ~49XRP/49USD. The rest is taken from the offer.
+
3999 testAMM(
+
4000 [&](AMM& ammAlice, Env& env) {
+
4001 env.fund(XRP(1'000), bob);
+
4002 env.close();
+
4003 env.trust(EUR(2'000), alice);
+
4004 env.close();
+
4005 env(pay(gw, alice, EUR(1'000)));
+
4006 env(offer(alice, XRP(101), EUR(100)), txflags(tfPassive));
+
4007 env.close();
+
4008 env(offer(alice, EUR(100), USD(100)), txflags(tfPassive));
+
4009 env.close();
+
4010 env(pay(bob, carol, USD(100)),
+
4011 path(~EUR, ~USD),
+
4012 sendmax(XRP(102)),
+
4013 txflags(tfPartialPayment));
+
4014 env.close();
+
4015 BEAST_EXPECT(ammAlice.expectBalances(
+
4016 XRPAmount(10'050'238'637),
+
4017 STAmount(USD, UINT64_C(9'950'01249687578), -11),
+
4018 ammAlice.tokens()));
+
4019 BEAST_EXPECT(expectOffers(
+
4020 env,
+
4021 alice,
+
4022 2,
+
4023 {{Amounts{
+
4024 XRPAmount(50'487'378),
+
4025 STAmount(EUR, UINT64_C(49'98750312422), -11)},
+
4026 Amounts{
+
4027 STAmount(EUR, UINT64_C(49'98750312422), -11),
+
4028 STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
+
4029 // Initial 30,000 + 99.99999999999
+
4030 BEAST_EXPECT(expectLine(
+
4031 env,
+
4032 carol,
+
4033 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
+
4034 // Initial 1,000 - 50238637(AMM pool) - 50512622(offer) - 10(tx
+
4035 // fee)
+
4036 BEAST_EXPECT(expectLedgerEntryRoot(
+
4037 env,
+
4038 bob,
+
4039 XRP(1'000) - XRPAmount{50'238'637} - XRPAmount{50'512'622} -
+
4040 txfee(env, 1)));
+
4041 },
+
4042 std::nullopt,
+
4043 0,
+
4044 std::nullopt,
+
4045 {features});
+
4046
+
4047 // Default path with AMM and Order Book offer. AMM is consumed first,
+
4048 // remaining amount is consumed by the offer.
+
4049 testAMM(
+
4050 [&](AMM& ammAlice, Env& env) {
+
4051 fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
+
4052 env.close();
+
4053 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
+
4054 env.close();
+
4055 env(pay(alice, carol, USD(200)),
+
4056 sendmax(XRP(200)),
+
4057 txflags(tfPartialPayment));
+
4058 env.close();
+
4059 if (!features[fixAMMv1_1])
+
4060 {
+
4061 BEAST_EXPECT(ammAlice.expectBalances(
+
4062 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
4063 // Initial 30,000 + 200
+
4064 BEAST_EXPECT(expectLine(env, carol, USD(30'200)));
+
4065 }
+
4066 else
+
4067 {
+
4068 BEAST_EXPECT(ammAlice.expectBalances(
+
4069 XRP(10'100),
+
4070 STAmount(USD, UINT64_C(10'000'00000000001), -11),
+
4071 ammAlice.tokens()));
+
4072 BEAST_EXPECT(expectLine(
+
4073 env,
+
4074 carol,
+
4075 STAmount(USD, UINT64_C(30'199'99999999999), -11)));
+
4076 }
+
4077 // Initial 30,000 - 10000(AMM pool LP) - 100(AMM offer) -
+
4078 // - 100(offer) - 10(tx fee) - one reserve
+
4079 BEAST_EXPECT(expectLedgerEntryRoot(
+
4080 env,
+
4081 alice,
+
4082 XRP(30'000) - XRP(10'000) - XRP(100) - XRP(100) -
+
4083 ammCrtFee(env) - txfee(env, 1)));
+
4084 BEAST_EXPECT(expectOffers(env, bob, 0));
+
4085 },
+
4086 {{XRP(10'000), USD(10'100)}},
+
4087 0,
+
4088 std::nullopt,
+
4089 {features});
+
4090
+
4091 // Default path with AMM and Order Book offer.
+
4092 // Order Book offer is consumed first.
+
4093 // Remaining amount is consumed by AMM.
+
4094 {
+
4095 Env env(*this, features);
+
4096 fund(env, gw, {alice, bob, carol}, XRP(20'000), {USD(2'000)});
+
4097 env.close();
+
4098 env(offer(bob, XRP(50), USD(150)), txflags(tfPassive));
+
4099 env.close();
+
4100 AMM ammAlice(env, alice, XRP(1'000), USD(1'050));
+
4101 env(pay(alice, carol, USD(200)),
+
4102 sendmax(XRP(200)),
+
4103 txflags(tfPartialPayment));
+
4104 env.close();
+
4105 BEAST_EXPECT(ammAlice.expectBalances(
+
4106 XRP(1'050), USD(1'000), ammAlice.tokens()));
+
4107 BEAST_EXPECT(expectLine(env, carol, USD(2'200)));
+
4108 BEAST_EXPECT(expectOffers(env, bob, 0));
+
4109 }
+
4110
+
4111 // Offer crossing XRP/IOU
+
4112 testAMM(
+
4113 [&](AMM& ammAlice, Env& env) {
+
4114 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
+
4115 env.close();
+
4116 env(offer(bob, USD(100), XRP(100)));
+
4117 env.close();
+
4118 BEAST_EXPECT(ammAlice.expectBalances(
+
4119 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
4120 // Initial 1,000 + 100
+
4121 BEAST_EXPECT(expectLine(env, bob, USD(1'100)));
+
4122 // Initial 30,000 - 100(offer) - 10(tx fee)
+
4123 BEAST_EXPECT(expectLedgerEntryRoot(
+
4124 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
+
4125 BEAST_EXPECT(expectOffers(env, bob, 0));
+
4126 },
+
4127 {{XRP(10'000), USD(10'100)}},
+
4128 0,
+
4129 std::nullopt,
+
4130 {features});
+
4131
+
4132 // Offer crossing IOU/IOU and transfer rate
+
4133 // Single path AMM offer
+
4134 testAMM(
+
4135 [&](AMM& ammAlice, Env& env) {
+
4136 env(rate(gw, 1.25));
+
4137 env.close();
+
4138 // This offer succeeds to cross pre- and post-amendment
+
4139 // because the strand's out amount is small enough to match
+
4140 // limitQuality value and limitOut() function in StrandFlow
+
4141 // doesn't require an adjustment to out value.
+
4142 env(offer(carol, EUR(100), GBP(100)));
+
4143 env.close();
+
4144 // No transfer fee
+
4145 BEAST_EXPECT(ammAlice.expectBalances(
+
4146 GBP(1'100), EUR(1'000), ammAlice.tokens()));
+
4147 // Initial 30,000 - 100(offer) - 25% transfer fee
+
4148 BEAST_EXPECT(expectLine(env, carol, GBP(29'875)));
+
4149 // Initial 30,000 + 100(offer)
+
4150 BEAST_EXPECT(expectLine(env, carol, EUR(30'100)));
+
4151 BEAST_EXPECT(expectOffers(env, bob, 0));
+
4152 },
+
4153 {{GBP(1'000), EUR(1'100)}},
+
4154 0,
+
4155 std::nullopt,
+
4156 {features});
+
4157 // Single-path AMM offer
+
4158 testAMM(
+
4159 [&](AMM& amm, Env& env) {
+
4160 env(rate(gw, 1.001));
+
4161 env.close();
+
4162 env(offer(carol, XRP(100), USD(55)));
+
4163 env.close();
+
4164 if (!features[fixAMMv1_1])
+
4165 {
+
4166 // Pre-amendment the transfer fee is not taken into
+
4167 // account when calculating the limit out based on
+
4168 // limitQuality. Carol pays 0.1% on the takerGets, which
+
4169 // lowers the overall quality. AMM offer is generated based
+
4170 // on higher limit out, which generates a larger offer
+
4171 // with lower quality. Consequently, the offer fails
+
4172 // to cross.
+
4173 BEAST_EXPECT(
+
4174 amm.expectBalances(XRP(1'000), USD(500), amm.tokens()));
+
4175 BEAST_EXPECT(expectOffers(
+
4176 env, carol, 1, {{Amounts{XRP(100), USD(55)}}}));
+
4177 }
+
4178 else
+
4179 {
+
4180 // Post-amendment the transfer fee is taken into account
+
4181 // when calculating the limit out based on limitQuality.
+
4182 // This increases the limitQuality and decreases
+
4183 // the limit out. Consequently, AMM offer size is decreased,
+
4184 // and the quality is increased, matching the overall
+
4185 // quality.
+
4186 // AMM offer ~50USD/91XRP
+
4187 BEAST_EXPECT(amm.expectBalances(
+
4188 XRPAmount(909'090'909),
+
4189 STAmount{USD, UINT64_C(550'000000055), -9},
+
4190 amm.tokens()));
+
4191 // Offer ~91XRP/49.99USD
+
4192 BEAST_EXPECT(expectOffers(
+
4193 env,
+
4194 carol,
+
4195 1,
+
4196 {{Amounts{
+
4197 XRPAmount{9'090'909},
+
4198 STAmount{USD, 4'99999995, -8}}}}));
+
4199 // Carol pays 0.1% fee on ~50USD =~ 0.05USD
+
4200 BEAST_EXPECT(
+
4201 env.balance(carol, USD) ==
+
4202 STAmount(USD, UINT64_C(29'949'94999999494), -11));
+
4203 }
+
4204 },
+
4205 {{XRP(1'000), USD(500)}},
+
4206 0,
+
4207 std::nullopt,
+
4208 {features});
+
4209 testAMM(
+
4210 [&](AMM& amm, Env& env) {
+
4211 env(rate(gw, 1.001));
+
4212 env.close();
+
4213 env(offer(carol, XRP(10), USD(5.5)));
+
4214 env.close();
+
4215 if (!features[fixAMMv1_1])
+
4216 {
+
4217 BEAST_EXPECT(amm.expectBalances(
+
4218 XRP(990),
+
4219 STAmount{USD, UINT64_C(505'050505050505), -12},
+
4220 amm.tokens()));
+
4221 BEAST_EXPECT(expectOffers(env, carol, 0));
+
4222 }
+
4223 else
+
4224 {
+
4225 BEAST_EXPECT(amm.expectBalances(
+
4226 XRP(990),
+
4227 STAmount{USD, UINT64_C(505'0505050505051), -13},
+
4228 amm.tokens()));
+
4229 BEAST_EXPECT(expectOffers(env, carol, 0));
+
4230 }
+
4231 },
+
4232 {{XRP(1'000), USD(500)}},
+
4233 0,
+
4234 std::nullopt,
+
4235 {features});
+
4236 // Multi-path AMM offer
+
4237 testAMM(
+
4238 [&](AMM& ammAlice, Env& env) {
+
4239 Account const ed("ed");
+
4240 fund(
+
4241 env,
+
4242 gw,
+
4243 {bob, ed},
+
4244 XRP(30'000),
+
4245 {GBP(2'000), EUR(2'000)},
+
4246 Fund::Acct);
+
4247 env(rate(gw, 1.25));
+
4248 env.close();
+
4249 // The auto-bridge is worse quality than AMM, is not consumed
+
4250 // first and initially forces multi-path AMM offer generation.
+
4251 // Multi-path AMM offers are consumed until their quality
+
4252 // is less than the auto-bridge offers quality. Auto-bridge
+
4253 // offers are consumed afterward. Then the behavior is
+
4254 // different pre-amendment and post-amendment.
+
4255 env(offer(bob, GBP(10), XRP(10)), txflags(tfPassive));
+
4256 env(offer(ed, XRP(10), EUR(10)), txflags(tfPassive));
+
4257 env.close();
+
4258 env(offer(carol, EUR(100), GBP(100)));
+
4259 env.close();
+
4260 if (!features[fixAMMv1_1])
+
4261 {
+
4262 // After the auto-bridge offers are consumed, single path
+
4263 // AMM offer is generated with the limit out not taking
+
4264 // into consideration the transfer fee. This results
+
4265 // in an overall lower quality offer than the limit quality
+
4266 // and the single path AMM offer fails to consume.
+
4267 // Total consumed ~37.06GBP/39.32EUR
+
4268 BEAST_EXPECT(ammAlice.expectBalances(
+
4269 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
+
4270 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
+
4271 ammAlice.tokens()));
+
4272 // Consumed offer ~49.32EUR/49.32GBP
+
4273 BEAST_EXPECT(expectOffers(
+
4274 env,
+
4275 carol,
+
4276 1,
+
4277 {Amounts{
+
4278 STAmount{EUR, UINT64_C(50'684828792831), -12},
+
4279 STAmount{GBP, UINT64_C(50'684828792831), -12}}}));
+
4280 BEAST_EXPECT(expectOffers(env, bob, 0));
+
4281 BEAST_EXPECT(expectOffers(env, ed, 0));
4282
-
4283 BEAST_EXPECT(expectLine(env, carol, USD(300)));
-
4284 }
-
4285
-
4286 // Dependent AMM
-
4287 {
-
4288 Env env(*this, features);
-
4289 auto const ETH = gw["ETH"];
-
4290 fund(
-
4291 env,
-
4292 gw,
-
4293 {alice},
-
4294 XRP(40'000),
-
4295 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
-
4296 fund(env, gw, {carol, bob}, XRP(1000), {USD(200)}, Fund::Acct);
-
4297 AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
-
4298 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
-
4299 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
-
4300 AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
-
4301 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
-
4302 env(pay(bob, carol, USD(100)),
-
4303 path(~EUR, ~BTC, ~USD),
-
4304 path(~ETH, ~EUR, ~BTC, ~USD),
-
4305 sendmax(XRP(200)));
-
4306 if (!features[fixAMMv1_1])
-
4307 {
-
4308 // XRP-EUR-BTC-USD path provides ~17.8USD/~18.7XRP
-
4309 // XRP-ETH-EUR-BTC-USD path provides ~82.2USD/82.4XRP
-
4310 BEAST_EXPECT(xrp_eur.expectBalances(
-
4311 XRPAmount(10'118'738'472),
-
4312 STAmount{EUR, UINT64_C(9'981'544436337968), -12},
-
4313 xrp_eur.tokens()));
-
4314 BEAST_EXPECT(eur_btc.expectBalances(
-
4315 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
-
4316 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
-
4317 eur_btc.tokens()));
-
4318 BEAST_EXPECT(btc_usd.expectBalances(
-
4319 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
-
4320 USD(9'900),
-
4321 btc_usd.tokens()));
-
4322 BEAST_EXPECT(xrp_eth.expectBalances(
-
4323 XRPAmount(10'082'446'397),
-
4324 STAmount{ETH, UINT64_C(10'017'41072778012), -11},
-
4325 xrp_eth.tokens()));
-
4326 BEAST_EXPECT(eth_eur.expectBalances(
-
4327 STAmount{ETH, UINT64_C(10'982'58927221988), -11},
-
4328 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
-
4329 eth_eur.tokens()));
-
4330 }
-
4331 else
-
4332 {
-
4333 BEAST_EXPECT(xrp_eur.expectBalances(
-
4334 XRPAmount(10'118'738'472),
-
4335 STAmount{EUR, UINT64_C(9'981'544436337923), -12},
-
4336 xrp_eur.tokens()));
-
4337 BEAST_EXPECT(eur_btc.expectBalances(
-
4338 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
-
4339 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
-
4340 eur_btc.tokens()));
-
4341 BEAST_EXPECT(btc_usd.expectBalances(
-
4342 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
-
4343 USD(9'900),
-
4344 btc_usd.tokens()));
-
4345 BEAST_EXPECT(xrp_eth.expectBalances(
-
4346 XRPAmount(10'082'446'397),
-
4347 STAmount{ETH, UINT64_C(10'017'41072777996), -11},
-
4348 xrp_eth.tokens()));
-
4349 BEAST_EXPECT(eth_eur.expectBalances(
-
4350 STAmount{ETH, UINT64_C(10'982'58927222004), -11},
-
4351 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
-
4352 eth_eur.tokens()));
-
4353 }
-
4354 BEAST_EXPECT(expectLine(env, carol, USD(300)));
-
4355 }
-
4356
-
4357 // AMM offers limit
-
4358 // Consuming 30 CLOB offers, results in hitting 30 AMM offers limit.
-
4359 testAMM(
-
4360 [&](AMM& ammAlice, Env& env) {
-
4361 env.fund(XRP(1'000), bob);
-
4362 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
-
4363 env(trust(alice, EUR(200)));
-
4364 for (int i = 0; i < 30; ++i)
-
4365 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
-
4366 // This is worse quality offer than 30 offers above.
-
4367 // It will not be consumed because of AMM offers limit.
-
4368 env(offer(alice, EUR(140), XRP(100)));
-
4369 env(pay(bob, carol, USD(100)),
-
4370 path(~XRP, ~USD),
-
4371 sendmax(EUR(400)),
-
4372 txflags(tfPartialPayment | tfNoRippleDirect));
-
4373 if (!features[fixAMMv1_1])
-
4374 {
-
4375 // Carol gets ~29.91USD because of the AMM offers limit
-
4376 BEAST_EXPECT(ammAlice.expectBalances(
-
4377 XRP(10'030),
-
4378 STAmount{USD, UINT64_C(9'970'089730807577), -12},
-
4379 ammAlice.tokens()));
-
4380 BEAST_EXPECT(expectLine(
-
4381 env,
-
4382 carol,
-
4383 STAmount{USD, UINT64_C(30'029'91026919241), -11}));
-
4384 }
-
4385 else
-
4386 {
-
4387 BEAST_EXPECT(ammAlice.expectBalances(
-
4388 XRP(10'030),
-
4389 STAmount{USD, UINT64_C(9'970'089730807827), -12},
-
4390 ammAlice.tokens()));
-
4391 BEAST_EXPECT(expectLine(
-
4392 env,
-
4393 carol,
-
4394 STAmount{USD, UINT64_C(30'029'91026919217), -11}));
-
4395 }
-
4396 BEAST_EXPECT(
-
4397 expectOffers(env, alice, 1, {{{EUR(140), XRP(100)}}}));
-
4398 },
-
4399 std::nullopt,
-
4400 0,
-
4401 std::nullopt,
-
4402 {features});
-
4403 // This payment is fulfilled
-
4404 testAMM(
-
4405 [&](AMM& ammAlice, Env& env) {
-
4406 env.fund(XRP(1'000), bob);
-
4407 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
-
4408 env(trust(alice, EUR(200)));
-
4409 for (int i = 0; i < 29; ++i)
-
4410 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
-
4411 // This is worse quality offer than 30 offers above.
-
4412 // It will not be consumed because of AMM offers limit.
-
4413 env(offer(alice, EUR(140), XRP(100)));
-
4414 env(pay(bob, carol, USD(100)),
-
4415 path(~XRP, ~USD),
-
4416 sendmax(EUR(400)),
-
4417 txflags(tfPartialPayment | tfNoRippleDirect));
-
4418 BEAST_EXPECT(ammAlice.expectBalances(
-
4419 XRPAmount{10'101'010'102}, USD(9'900), ammAlice.tokens()));
-
4420 if (!features[fixAMMv1_1])
-
4421 {
-
4422 // Carol gets ~100USD
-
4423 BEAST_EXPECT(expectLine(
-
4424 env,
-
4425 carol,
-
4426 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
-
4427 }
-
4428 else
-
4429 {
-
4430 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
4431 }
-
4432 BEAST_EXPECT(expectOffers(
-
4433 env,
-
4434 alice,
-
4435 1,
-
4436 {{{STAmount{EUR, UINT64_C(39'1858572), -7},
-
4437 XRPAmount{27'989'898}}}}));
-
4438 },
-
4439 std::nullopt,
-
4440 0,
-
4441 std::nullopt,
-
4442 {features});
-
4443
-
4444 // Offer crossing with AMM and another offer. AMM has a better
-
4445 // quality and is consumed first.
+
4283 // Initial 30,000 - ~47.06(offers = 37.06(AMM) + 10(LOB))
+
4284 // * 1.25
+
4285 // = 58.825 = ~29941.17
+
4286 // carol bought ~72.93EUR at the cost of ~70.68GBP
+
4287 // the offer is partially consumed
+
4288 BEAST_EXPECT(expectLine(
+
4289 env,
+
4290 carol,
+
4291 STAmount{GBP, UINT64_C(29'941'16770347333), -11}));
+
4292 // Initial 30,000 + ~49.3(offers = 39.3(AMM) + 10(LOB))
+
4293 BEAST_EXPECT(expectLine(
+
4294 env,
+
4295 carol,
+
4296 STAmount{EUR, UINT64_C(30'049'31517120716), -11}));
+
4297 }
+
4298 else
+
4299 {
+
4300 // After the auto-bridge offers are consumed, single path
+
4301 // AMM offer is generated with the limit out taking
+
4302 // into consideration the transfer fee. This results
+
4303 // in an overall quality offer matching the limit quality
+
4304 // and the single path AMM offer is consumed. More
+
4305 // liquidity is consumed overall in post-amendment.
+
4306 // Total consumed ~60.68GBP/62.93EUR
+
4307 BEAST_EXPECT(ammAlice.expectBalances(
+
4308 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
+
4309 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
+
4310 ammAlice.tokens()));
+
4311 // Consumed offer ~72.93EUR/72.93GBP
+
4312 BEAST_EXPECT(expectOffers(
+
4313 env,
+
4314 carol,
+
4315 1,
+
4316 {Amounts{
+
4317 STAmount{EUR, UINT64_C(27'06583722134028), -14},
+
4318 STAmount{GBP, UINT64_C(27'06583722134028), -14}}}));
+
4319 BEAST_EXPECT(expectOffers(env, bob, 0));
+
4320 BEAST_EXPECT(expectOffers(env, ed, 0));
+
4321
+
4322 // Initial 30,000 - ~70.68(offers = 60.68(AMM) + 10(LOB))
+
4323 // * 1.25
+
4324 // = 88.35 = ~29911.64
+
4325 // carol bought ~72.93EUR at the cost of ~70.68GBP
+
4326 // the offer is partially consumed
+
4327 BEAST_EXPECT(expectLine(
+
4328 env,
+
4329 carol,
+
4330 STAmount{GBP, UINT64_C(29'911'64396400896), -11}));
+
4331 // Initial 30,000 + ~72.93(offers = 62.93(AMM) + 10(LOB))
+
4332 BEAST_EXPECT(expectLine(
+
4333 env,
+
4334 carol,
+
4335 STAmount{EUR, UINT64_C(30'072'93416277865), -11}));
+
4336 }
+
4337 // Initial 2000 + 10 = 2010
+
4338 BEAST_EXPECT(expectLine(env, bob, GBP(2'010)));
+
4339 // Initial 2000 - 10 * 1.25 = 1987.5
+
4340 BEAST_EXPECT(expectLine(env, ed, EUR(1'987.5)));
+
4341 },
+
4342 {{GBP(1'000), EUR(1'100)}},
+
4343 0,
+
4344 std::nullopt,
+
4345 {features});
+
4346
+
4347 // Payment and transfer fee
+
4348 // Scenario:
+
4349 // Bob sends 125GBP to pay 80EUR to Carol
+
4350 // Payment execution:
+
4351 // bob's 125GBP/1.25 = 100GBP
+
4352 // 100GBP/100EUR AMM offer
+
4353 // 100EUR/1.25 = 80EUR paid to carol
+
4354 testAMM(
+
4355 [&](AMM& ammAlice, Env& env) {
+
4356 fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
+
4357 env(rate(gw, 1.25));
+
4358 env.close();
+
4359 env(pay(bob, carol, EUR(100)),
+
4360 path(~EUR),
+
4361 sendmax(GBP(125)),
+
4362 txflags(tfPartialPayment));
+
4363 env.close();
+
4364 BEAST_EXPECT(ammAlice.expectBalances(
+
4365 GBP(1'100), EUR(1'000), ammAlice.tokens()));
+
4366 BEAST_EXPECT(expectLine(env, bob, GBP(75)));
+
4367 BEAST_EXPECT(expectLine(env, carol, EUR(30'080)));
+
4368 },
+
4369 {{GBP(1'000), EUR(1'100)}},
+
4370 0,
+
4371 std::nullopt,
+
4372 {features});
+
4373
+
4374 // Payment and transfer fee, multiple steps
+
4375 // Scenario:
+
4376 // Dan's offer 200CAN/200GBP
+
4377 // AMM 1000GBP/10125EUR
+
4378 // Ed's offer 200EUR/200USD
+
4379 // Bob sends 195.3125CAN to pay 100USD to Carol
+
4380 // Payment execution:
+
4381 // bob's 195.3125CAN/1.25 = 156.25CAN -> dan's offer
+
4382 // 156.25CAN/156.25GBP 156.25GBP/1.25 = 125GBP -> AMM's offer
+
4383 // 125GBP/125EUR 125EUR/1.25 = 100EUR -> ed's offer
+
4384 // 100EUR/100USD 100USD/1.25 = 80USD paid to carol
+
4385 testAMM(
+
4386 [&](AMM& ammAlice, Env& env) {
+
4387 Account const dan("dan");
+
4388 Account const ed("ed");
+
4389 auto const CAN = gw["CAN"];
+
4390 fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
+
4391 fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
+
4392 fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
+
4393 env(trust(carol, USD(100)));
+
4394 env(rate(gw, 1.25));
+
4395 env.close();
+
4396 env(offer(dan, CAN(200), GBP(200)));
+
4397 env(offer(ed, EUR(200), USD(200)));
+
4398 env.close();
+
4399 env(pay(bob, carol, USD(100)),
+
4400 path(~GBP, ~EUR, ~USD),
+
4401 sendmax(CAN(195.3125)),
+
4402 txflags(tfPartialPayment));
+
4403 env.close();
+
4404 BEAST_EXPECT(expectLine(env, bob, CAN(0)));
+
4405 BEAST_EXPECT(expectLine(env, dan, CAN(356.25), GBP(43.75)));
+
4406 BEAST_EXPECT(ammAlice.expectBalances(
+
4407 GBP(10'125), EUR(10'000), ammAlice.tokens()));
+
4408 BEAST_EXPECT(expectLine(env, ed, EUR(300), USD(100)));
+
4409 BEAST_EXPECT(expectLine(env, carol, USD(80)));
+
4410 },
+
4411 {{GBP(10'000), EUR(10'125)}},
+
4412 0,
+
4413 std::nullopt,
+
4414 {features});
+
4415
+
4416 // Pay amounts close to one side of the pool
+
4417 testAMM(
+
4418 [&](AMM& ammAlice, Env& env) {
+
4419 env(pay(alice, carol, USD(99.99)),
+
4420 path(~USD),
+
4421 sendmax(XRP(1)),
+
4422 txflags(tfPartialPayment),
+
4423 ter(tesSUCCESS));
+
4424 env(pay(alice, carol, USD(100)),
+
4425 path(~USD),
+
4426 sendmax(XRP(1)),
+
4427 txflags(tfPartialPayment),
+
4428 ter(tesSUCCESS));
+
4429 env(pay(alice, carol, XRP(100)),
+
4430 path(~XRP),
+
4431 sendmax(USD(1)),
+
4432 txflags(tfPartialPayment),
+
4433 ter(tesSUCCESS));
+
4434 env(pay(alice, carol, STAmount{xrpIssue(), 99'999'900}),
+
4435 path(~XRP),
+
4436 sendmax(USD(1)),
+
4437 txflags(tfPartialPayment),
+
4438 ter(tesSUCCESS));
+
4439 },
+
4440 {{XRP(100), USD(100)}},
+
4441 0,
+
4442 std::nullopt,
+
4443 {features});
+
4444
+
4445 // Multiple paths/steps
4446 {
4447 Env env(*this, features);
-
4448 fund(env, gw, {alice, carol, bob}, XRP(30'000), {USD(30'000)});
-
4449 env(offer(bob, XRP(100), USD(100.001)));
-
4450 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
-
4451 env(offer(carol, USD(100), XRP(100)));
-
4452 if (!features[fixAMMv1_1])
-
4453 {
-
4454 BEAST_EXPECT(ammAlice.expectBalances(
-
4455 XRPAmount{10'049'825'373},
-
4456 STAmount{USD, UINT64_C(10'049'92586949302), -11},
-
4457 ammAlice.tokens()));
-
4458 BEAST_EXPECT(expectOffers(
-
4459 env,
-
4460 bob,
-
4461 1,
-
4462 {{{XRPAmount{50'074'629},
-
4463 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
-
4464 }
-
4465 else
-
4466 {
-
4467 BEAST_EXPECT(ammAlice.expectBalances(
-
4468 XRPAmount{10'049'825'372},
-
4469 STAmount{USD, UINT64_C(10'049'92587049303), -11},
-
4470 ammAlice.tokens()));
-
4471 BEAST_EXPECT(expectOffers(
-
4472 env,
-
4473 bob,
-
4474 1,
-
4475 {{{XRPAmount{50'074'628},
-
4476 STAmount{USD, UINT64_C(50'07512950697), -11}}}}));
-
4477 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
4478 }
-
4479 }
-
4480
-
4481 // Individually frozen account
-
4482 testAMM(
-
4483 [&](AMM& ammAlice, Env& env) {
-
4484 env(trust(gw, carol["USD"](0), tfSetFreeze));
-
4485 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
4486 env.close();
-
4487 env(pay(alice, carol, USD(1)),
-
4488 path(~USD),
-
4489 sendmax(XRP(10)),
-
4490 txflags(tfNoRippleDirect | tfPartialPayment),
-
4491 ter(tesSUCCESS));
-
4492 },
-
4493 std::nullopt,
-
4494 0,
-
4495 std::nullopt,
-
4496 {features});
-
4497 }
-
4498
-
4499 void
-
4500 testAMMTokens()
-
4501 {
-
4502 testcase("AMM Tokens");
-
4503 using namespace jtx;
-
4504
-
4505 // Offer crossing with AMM LPTokens and XRP.
-
4506 testAMM([&](AMM& ammAlice, Env& env) {
-
4507 auto const baseFee = env.current()->fees().base.drops();
-
4508 auto const token1 = ammAlice.lptIssue();
-
4509 auto priceXRP = withdrawByTokens(
-
4510 STAmount{XRPAmount{10'000'000'000}},
-
4511 STAmount{token1, 10'000'000},
-
4512 STAmount{token1, 5'000'000},
-
4513 0);
-
4514 // Carol places an order to buy LPTokens
-
4515 env(offer(carol, STAmount{token1, 5'000'000}, priceXRP));
-
4516 // Alice places an order to sell LPTokens
-
4517 env(offer(alice, priceXRP, STAmount{token1, 5'000'000}));
-
4518 // Pool's LPTokens balance doesn't change
-
4519 BEAST_EXPECT(ammAlice.expectBalances(
-
4520 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
-
4521 // Carol is Liquidity Provider
-
4522 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{5'000'000}));
-
4523 BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
-
4524 // Carol votes
-
4525 ammAlice.vote(carol, 1'000);
-
4526 BEAST_EXPECT(ammAlice.expectTradingFee(500));
-
4527 ammAlice.vote(carol, 0);
-
4528 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
4529 // Carol bids
-
4530 env(ammAlice.bid({.account = carol, .bidMin = 100}));
-
4531 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4'999'900}));
-
4532 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{100}));
-
4533 BEAST_EXPECT(
-
4534 accountBalance(env, carol) ==
-
4535 std::to_string(22500000000 - 4 * baseFee));
-
4536 priceXRP = withdrawByTokens(
-
4537 STAmount{XRPAmount{10'000'000'000}},
-
4538 STAmount{token1, 9'999'900},
-
4539 STAmount{token1, 4'999'900},
-
4540 0);
-
4541 // Carol withdraws
-
4542 ammAlice.withdrawAll(carol, XRP(0));
-
4543 BEAST_EXPECT(
-
4544 accountBalance(env, carol) ==
-
4545 std::to_string(29999949999 - 5 * baseFee));
-
4546 BEAST_EXPECT(ammAlice.expectBalances(
-
4547 XRPAmount{10'000'000'000} - priceXRP,
-
4548 USD(10'000),
-
4549 IOUAmount{5'000'000}));
-
4550 BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
-
4551 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
4552 });
-
4553
-
4554 // Offer crossing with two AMM LPTokens.
-
4555 testAMM([&](AMM& ammAlice, Env& env) {
-
4556 ammAlice.deposit(carol, 1'000'000);
-
4557 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
-
4558 AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000));
-
4559 ammAlice1.deposit(carol, 1'000'000);
-
4560 auto const token1 = ammAlice.lptIssue();
-
4561 auto const token2 = ammAlice1.lptIssue();
-
4562 env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100}),
-
4563 txflags(tfPassive));
-
4564 env.close();
-
4565 BEAST_EXPECT(expectOffers(env, alice, 1));
-
4566 env(offer(carol, STAmount{token2, 100}, STAmount{token1, 100}));
-
4567 env.close();
-
4568 BEAST_EXPECT(
-
4569 expectLine(env, alice, STAmount{token1, 10'000'100}) &&
-
4570 expectLine(env, alice, STAmount{token2, 9'999'900}));
-
4571 BEAST_EXPECT(
-
4572 expectLine(env, carol, STAmount{token2, 1'000'100}) &&
-
4573 expectLine(env, carol, STAmount{token1, 999'900}));
-
4574 BEAST_EXPECT(
-
4575 expectOffers(env, alice, 0) && expectOffers(env, carol, 0));
-
4576 });
-
4577
-
4578 // LPs pay LPTokens directly. Must trust set because the trust line
-
4579 // is checked for the limit, which is 0 in the AMM auto-created
-
4580 // trust line.
-
4581 testAMM([&](AMM& ammAlice, Env& env) {
-
4582 auto const token1 = ammAlice.lptIssue();
-
4583 env.trust(STAmount{token1, 2'000'000}, carol);
-
4584 env.close();
-
4585 ammAlice.deposit(carol, 1'000'000);
-
4586 BEAST_EXPECT(
-
4587 ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
-
4588 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
-
4589 // Pool balance doesn't change, only tokens moved from
-
4590 // one line to another.
-
4591 env(pay(alice, carol, STAmount{token1, 100}));
-
4592 env.close();
-
4593 BEAST_EXPECT(
-
4594 // Alice initial token1 10,000,000 - 100
-
4595 ammAlice.expectLPTokens(alice, IOUAmount{9'999'900, 0}) &&
-
4596 // Carol initial token1 1,000,000 + 100
-
4597 ammAlice.expectLPTokens(carol, IOUAmount{1'000'100, 0}));
-
4598
-
4599 env.trust(STAmount{token1, 20'000'000}, alice);
-
4600 env.close();
-
4601 env(pay(carol, alice, STAmount{token1, 100}));
-
4602 env.close();
-
4603 // Back to the original balance
-
4604 BEAST_EXPECT(
-
4605 ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
-
4606 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
-
4607 });
-
4608 }
-
4609
-
4610 void
-
4611 testAmendment()
-
4612 {
-
4613 testcase("Amendment");
-
4614 using namespace jtx;
-
4615 FeatureBitset const all{supported_amendments()};
-
4616 FeatureBitset const noAMM{all - featureAMM};
-
4617 FeatureBitset const noNumber{all - fixUniversalNumber};
-
4618 FeatureBitset const noAMMAndNumber{
-
4619 all - featureAMM - fixUniversalNumber};
-
4620
-
4621 for (auto const& feature : {noAMM, noNumber, noAMMAndNumber})
-
4622 {
-
4623 Env env{*this, feature};
-
4624 fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
-
4625 AMM amm(env, alice, XRP(1'000), USD(1'000), ter(temDISABLED));
-
4626
-
4627 env(amm.bid({.bidMax = 1000}), ter(temMALFORMED));
-
4628 env(amm.bid({}), ter(temDISABLED));
-
4629 amm.vote(VoteArg{.tfee = 100, .err = ter(temDISABLED)});
-
4630 amm.withdraw(WithdrawArg{.tokens = 100, .err = ter(temMALFORMED)});
-
4631 amm.withdraw(WithdrawArg{.err = ter(temDISABLED)});
-
4632 amm.deposit(
-
4633 DepositArg{.asset1In = USD(100), .err = ter(temDISABLED)});
-
4634 amm.ammDelete(alice, ter(temDISABLED));
-
4635 }
-
4636 }
-
4637
-
4638 void
-
4639 testFlags()
-
4640 {
-
4641 testcase("Flags");
-
4642 using namespace jtx;
-
4643
-
4644 testAMM([&](AMM& ammAlice, Env& env) {
-
4645 auto const info = env.rpc(
-
4646 "json",
-
4647 "account_info",
-
4648 std::string(
-
4649 "{\"account\": \"" + to_string(ammAlice.ammAccount()) +
-
4650 "\"}"));
-
4651 auto const flags =
-
4652 info[jss::result][jss::account_data][jss::Flags].asUInt();
-
4653 BEAST_EXPECT(
-
4654 flags ==
-
4655 (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
-
4656 });
-
4657 }
-
4658
-
4659 void
-
4660 testRippling()
-
4661 {
-
4662 testcase("Rippling");
-
4663 using namespace jtx;
-
4664
-
4665 // Rippling via AMM fails because AMM trust line has 0 limit.
-
4666 // Set up two issuers, A and B. Have each issue a token called TST.
-
4667 // Have another account C hold TST from both issuers,
-
4668 // and create an AMM for this pair.
-
4669 // Have a fourth account, D, create a trust line to the AMM for TST.
-
4670 // Send a payment delivering TST.AMM from C to D, using SendMax in
-
4671 // TST.A (or B) and a path through the AMM account. By normal
-
4672 // rippling rules, this would have caused the AMM's balances
-
4673 // to shift at a 1:1 rate with no fee applied has it not been
-
4674 // for 0 limit.
-
4675 {
-
4676 Env env(*this);
-
4677 auto const A = Account("A");
-
4678 auto const B = Account("B");
-
4679 auto const TSTA = A["TST"];
-
4680 auto const TSTB = B["TST"];
-
4681 auto const C = Account("C");
-
4682 auto const D = Account("D");
-
4683
-
4684 env.fund(XRP(10'000), A);
-
4685 env.fund(XRP(10'000), B);
-
4686 env.fund(XRP(10'000), C);
-
4687 env.fund(XRP(10'000), D);
-
4688
-
4689 env.trust(TSTA(10'000), C);
-
4690 env.trust(TSTB(10'000), C);
-
4691 env(pay(A, C, TSTA(10'000)));
-
4692 env(pay(B, C, TSTB(10'000)));
-
4693 AMM amm(env, C, TSTA(5'000), TSTB(5'000));
-
4694 auto const ammIss = Issue(TSTA.currency, amm.ammAccount());
-
4695
-
4696 // Can SetTrust only for AMM LP tokens
-
4697 env(trust(D, STAmount{ammIss, 10'000}), ter(tecNO_PERMISSION));
-
4698 env.close();
-
4699
-
4700 // The payment would fail because of above, but check just in case
-
4701 env(pay(C, D, STAmount{ammIss, 10}),
-
4702 sendmax(TSTA(100)),
-
4703 path(amm.ammAccount()),
-
4704 txflags(tfPartialPayment | tfNoRippleDirect),
-
4705 ter(tecPATH_DRY));
-
4706 }
-
4707 }
-
4708
-
4709 void
-
4710 testAMMAndCLOB(FeatureBitset features)
-
4711 {
-
4712 testcase("AMMAndCLOB, offer quality change");
-
4713 using namespace jtx;
-
4714 auto const gw = Account("gw");
-
4715 auto const TST = gw["TST"];
-
4716 auto const LP1 = Account("LP1");
-
4717 auto const LP2 = Account("LP2");
-
4718
-
4719 auto prep = [&](auto const& offerCb, auto const& expectCb) {
-
4720 Env env(*this, features);
-
4721 env.fund(XRP(30'000'000'000), gw);
-
4722 env(offer(gw, XRP(11'500'000'000), TST(1'000'000'000)));
+
4448 auto const ETH = gw["ETH"];
+
4449 fund(
+
4450 env,
+
4451 gw,
+
4452 {alice},
+
4453 XRP(100'000),
+
4454 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
+
4455 fund(env, gw, {carol, bob}, XRP(1'000), {USD(200)}, Fund::Acct);
+
4456 AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
+
4457 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
+
4458 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
+
4459 AMM xrp_usd(env, alice, XRP(10'150), USD(10'200));
+
4460 AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
+
4461 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
+
4462 AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
+
4463 env(pay(bob, carol, USD(100)),
+
4464 path(~EUR, ~BTC, ~USD),
+
4465 path(~USD),
+
4466 path(~ETH, ~EUR, ~USD),
+
4467 sendmax(XRP(200)));
+
4468 if (!features[fixAMMv1_1])
+
4469 {
+
4470 // XRP-ETH-EUR-USD
+
4471 // This path provides ~26.06USD/26.2XRP
+
4472 BEAST_EXPECT(xrp_eth.expectBalances(
+
4473 XRPAmount(10'026'208'900),
+
4474 STAmount{ETH, UINT64_C(10'073'65779244494), -11},
+
4475 xrp_eth.tokens()));
+
4476 BEAST_EXPECT(eth_eur.expectBalances(
+
4477 STAmount{ETH, UINT64_C(10'926'34220755506), -11},
+
4478 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
+
4479 eth_eur.tokens()));
+
4480 BEAST_EXPECT(eur_usd.expectBalances(
+
4481 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
+
4482 STAmount{USD, UINT64_C(9'973'93151712086), -11},
+
4483 eur_usd.tokens()));
+
4484 // XRP-USD path
+
4485 // This path provides ~73.9USD/74.1XRP
+
4486 BEAST_EXPECT(xrp_usd.expectBalances(
+
4487 XRPAmount(10'224'106'246),
+
4488 STAmount{USD, UINT64_C(10'126'06848287914), -11},
+
4489 xrp_usd.tokens()));
+
4490 }
+
4491 else
+
4492 {
+
4493 BEAST_EXPECT(xrp_eth.expectBalances(
+
4494 XRPAmount(10'026'208'900),
+
4495 STAmount{ETH, UINT64_C(10'073'65779244461), -11},
+
4496 xrp_eth.tokens()));
+
4497 BEAST_EXPECT(eth_eur.expectBalances(
+
4498 STAmount{ETH, UINT64_C(10'926'34220755539), -11},
+
4499 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
+
4500 eth_eur.tokens()));
+
4501 BEAST_EXPECT(eur_usd.expectBalances(
+
4502 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
+
4503 STAmount{USD, UINT64_C(9'973'93151712057), -11},
+
4504 eur_usd.tokens()));
+
4505 // XRP-USD path
+
4506 // This path provides ~73.9USD/74.1XRP
+
4507 BEAST_EXPECT(xrp_usd.expectBalances(
+
4508 XRPAmount(10'224'106'246),
+
4509 STAmount{USD, UINT64_C(10'126'06848287943), -11},
+
4510 xrp_usd.tokens()));
+
4511 }
+
4512
+
4513 // XRP-EUR-BTC-USD
+
4514 // This path doesn't provide any liquidity due to how
+
4515 // offers are generated in multi-path. Analytical solution
+
4516 // shows a different distribution:
+
4517 // XRP-EUR-BTC-USD 11.6USD/11.64XRP, XRP-USD 60.7USD/60.8XRP,
+
4518 // XRP-ETH-EUR-USD 27.6USD/27.6XRP
+
4519 BEAST_EXPECT(xrp_eur.expectBalances(
+
4520 XRP(10'100), EUR(10'000), xrp_eur.tokens()));
+
4521 BEAST_EXPECT(eur_btc.expectBalances(
+
4522 EUR(10'000), BTC(10'200), eur_btc.tokens()));
+
4523 BEAST_EXPECT(btc_usd.expectBalances(
+
4524 BTC(10'100), USD(10'000), btc_usd.tokens()));
+
4525
+
4526 BEAST_EXPECT(expectLine(env, carol, USD(300)));
+
4527 }
+
4528
+
4529 // Dependent AMM
+
4530 {
+
4531 Env env(*this, features);
+
4532 auto const ETH = gw["ETH"];
+
4533 fund(
+
4534 env,
+
4535 gw,
+
4536 {alice},
+
4537 XRP(40'000),
+
4538 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
+
4539 fund(env, gw, {carol, bob}, XRP(1000), {USD(200)}, Fund::Acct);
+
4540 AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
+
4541 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
+
4542 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
+
4543 AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
+
4544 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
+
4545 env(pay(bob, carol, USD(100)),
+
4546 path(~EUR, ~BTC, ~USD),
+
4547 path(~ETH, ~EUR, ~BTC, ~USD),
+
4548 sendmax(XRP(200)));
+
4549 if (!features[fixAMMv1_1])
+
4550 {
+
4551 // XRP-EUR-BTC-USD path provides ~17.8USD/~18.7XRP
+
4552 // XRP-ETH-EUR-BTC-USD path provides ~82.2USD/82.4XRP
+
4553 BEAST_EXPECT(xrp_eur.expectBalances(
+
4554 XRPAmount(10'118'738'472),
+
4555 STAmount{EUR, UINT64_C(9'981'544436337968), -12},
+
4556 xrp_eur.tokens()));
+
4557 BEAST_EXPECT(eur_btc.expectBalances(
+
4558 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
+
4559 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
+
4560 eur_btc.tokens()));
+
4561 BEAST_EXPECT(btc_usd.expectBalances(
+
4562 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
+
4563 USD(9'900),
+
4564 btc_usd.tokens()));
+
4565 BEAST_EXPECT(xrp_eth.expectBalances(
+
4566 XRPAmount(10'082'446'397),
+
4567 STAmount{ETH, UINT64_C(10'017'41072778012), -11},
+
4568 xrp_eth.tokens()));
+
4569 BEAST_EXPECT(eth_eur.expectBalances(
+
4570 STAmount{ETH, UINT64_C(10'982'58927221988), -11},
+
4571 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
+
4572 eth_eur.tokens()));
+
4573 }
+
4574 else
+
4575 {
+
4576 BEAST_EXPECT(xrp_eur.expectBalances(
+
4577 XRPAmount(10'118'738'472),
+
4578 STAmount{EUR, UINT64_C(9'981'544436337923), -12},
+
4579 xrp_eur.tokens()));
+
4580 BEAST_EXPECT(eur_btc.expectBalances(
+
4581 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
+
4582 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
+
4583 eur_btc.tokens()));
+
4584 BEAST_EXPECT(btc_usd.expectBalances(
+
4585 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
+
4586 USD(9'900),
+
4587 btc_usd.tokens()));
+
4588 BEAST_EXPECT(xrp_eth.expectBalances(
+
4589 XRPAmount(10'082'446'397),
+
4590 STAmount{ETH, UINT64_C(10'017'41072777996), -11},
+
4591 xrp_eth.tokens()));
+
4592 BEAST_EXPECT(eth_eur.expectBalances(
+
4593 STAmount{ETH, UINT64_C(10'982'58927222004), -11},
+
4594 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
+
4595 eth_eur.tokens()));
+
4596 }
+
4597 BEAST_EXPECT(expectLine(env, carol, USD(300)));
+
4598 }
+
4599
+
4600 // AMM offers limit
+
4601 // Consuming 30 CLOB offers, results in hitting 30 AMM offers limit.
+
4602 testAMM(
+
4603 [&](AMM& ammAlice, Env& env) {
+
4604 env.fund(XRP(1'000), bob);
+
4605 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
+
4606 env(trust(alice, EUR(200)));
+
4607 for (int i = 0; i < 30; ++i)
+
4608 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
+
4609 // This is worse quality offer than 30 offers above.
+
4610 // It will not be consumed because of AMM offers limit.
+
4611 env(offer(alice, EUR(140), XRP(100)));
+
4612 env(pay(bob, carol, USD(100)),
+
4613 path(~XRP, ~USD),
+
4614 sendmax(EUR(400)),
+
4615 txflags(tfPartialPayment | tfNoRippleDirect));
+
4616 if (!features[fixAMMv1_1])
+
4617 {
+
4618 // Carol gets ~29.91USD because of the AMM offers limit
+
4619 BEAST_EXPECT(ammAlice.expectBalances(
+
4620 XRP(10'030),
+
4621 STAmount{USD, UINT64_C(9'970'089730807577), -12},
+
4622 ammAlice.tokens()));
+
4623 BEAST_EXPECT(expectLine(
+
4624 env,
+
4625 carol,
+
4626 STAmount{USD, UINT64_C(30'029'91026919241), -11}));
+
4627 }
+
4628 else
+
4629 {
+
4630 BEAST_EXPECT(ammAlice.expectBalances(
+
4631 XRP(10'030),
+
4632 STAmount{USD, UINT64_C(9'970'089730807827), -12},
+
4633 ammAlice.tokens()));
+
4634 BEAST_EXPECT(expectLine(
+
4635 env,
+
4636 carol,
+
4637 STAmount{USD, UINT64_C(30'029'91026919217), -11}));
+
4638 }
+
4639 BEAST_EXPECT(
+
4640 expectOffers(env, alice, 1, {{{EUR(140), XRP(100)}}}));
+
4641 },
+
4642 std::nullopt,
+
4643 0,
+
4644 std::nullopt,
+
4645 {features});
+
4646 // This payment is fulfilled
+
4647 testAMM(
+
4648 [&](AMM& ammAlice, Env& env) {
+
4649 env.fund(XRP(1'000), bob);
+
4650 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
+
4651 env(trust(alice, EUR(200)));
+
4652 for (int i = 0; i < 29; ++i)
+
4653 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
+
4654 // This is worse quality offer than 30 offers above.
+
4655 // It will not be consumed because of AMM offers limit.
+
4656 env(offer(alice, EUR(140), XRP(100)));
+
4657 env(pay(bob, carol, USD(100)),
+
4658 path(~XRP, ~USD),
+
4659 sendmax(EUR(400)),
+
4660 txflags(tfPartialPayment | tfNoRippleDirect));
+
4661 BEAST_EXPECT(ammAlice.expectBalances(
+
4662 XRPAmount{10'101'010'102}, USD(9'900), ammAlice.tokens()));
+
4663 if (!features[fixAMMv1_1])
+
4664 {
+
4665 // Carol gets ~100USD
+
4666 BEAST_EXPECT(expectLine(
+
4667 env,
+
4668 carol,
+
4669 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
+
4670 }
+
4671 else
+
4672 {
+
4673 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
4674 }
+
4675 BEAST_EXPECT(expectOffers(
+
4676 env,
+
4677 alice,
+
4678 1,
+
4679 {{{STAmount{EUR, UINT64_C(39'1858572), -7},
+
4680 XRPAmount{27'989'898}}}}));
+
4681 },
+
4682 std::nullopt,
+
4683 0,
+
4684 std::nullopt,
+
4685 {features});
+
4686
+
4687 // Offer crossing with AMM and another offer. AMM has a better
+
4688 // quality and is consumed first.
+
4689 {
+
4690 Env env(*this, features);
+
4691 fund(env, gw, {alice, carol, bob}, XRP(30'000), {USD(30'000)});
+
4692 env(offer(bob, XRP(100), USD(100.001)));
+
4693 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
+
4694 env(offer(carol, USD(100), XRP(100)));
+
4695 if (!features[fixAMMv1_1])
+
4696 {
+
4697 BEAST_EXPECT(ammAlice.expectBalances(
+
4698 XRPAmount{10'049'825'373},
+
4699 STAmount{USD, UINT64_C(10'049'92586949302), -11},
+
4700 ammAlice.tokens()));
+
4701 BEAST_EXPECT(expectOffers(
+
4702 env,
+
4703 bob,
+
4704 1,
+
4705 {{{XRPAmount{50'074'629},
+
4706 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
+
4707 }
+
4708 else
+
4709 {
+
4710 BEAST_EXPECT(ammAlice.expectBalances(
+
4711 XRPAmount{10'049'825'372},
+
4712 STAmount{USD, UINT64_C(10'049'92587049303), -11},
+
4713 ammAlice.tokens()));
+
4714 BEAST_EXPECT(expectOffers(
+
4715 env,
+
4716 bob,
+
4717 1,
+
4718 {{{XRPAmount{50'074'628},
+
4719 STAmount{USD, UINT64_C(50'07512950697), -11}}}}));
+
4720 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
4721 }
+
4722 }
4723
-
4724 env.fund(XRP(10'000), LP1);
-
4725 env.fund(XRP(10'000), LP2);
-
4726 env(offer(LP1, TST(25), XRPAmount(287'500'000)));
-
4727
-
4728 // Either AMM or CLOB offer
-
4729 offerCb(env);
-
4730
-
4731 env(offer(LP2, TST(25), XRPAmount(287'500'000)));
-
4732
-
4733 expectCb(env);
-
4734 };
-
4735
-
4736 // If we replace AMM with an equivalent CLOB offer, which AMM generates
-
4737 // when it is consumed, then the result must be equivalent, too.
-
4738 std::string lp2TSTBalance;
-
4739 std::string lp2TakerGets;
-
4740 std::string lp2TakerPays;
-
4741 // Execute with AMM first
-
4742 prep(
-
4743 [&](Env& env) { AMM amm(env, LP1, TST(25), XRP(250)); },
-
4744 [&](Env& env) {
-
4745 lp2TSTBalance =
-
4746 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
-
4747 .asString();
-
4748 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
-
4749 lp2TakerGets = offer["taker_gets"].asString();
-
4750 lp2TakerPays = offer["taker_pays"]["value"].asString();
-
4751 });
-
4752 // Execute with CLOB offer
-
4753 prep(
-
4754 [&](Env& env) {
-
4755 if (!features[fixAMMv1_1])
-
4756 env(offer(
-
4757 LP1,
-
4758 XRPAmount{18'095'133},
-
4759 STAmount{TST, UINT64_C(1'68737984885388), -14}),
-
4760 txflags(tfPassive));
-
4761 else
-
4762 env(offer(
-
4763 LP1,
-
4764 XRPAmount{18'095'132},
-
4765 STAmount{TST, UINT64_C(1'68737976189735), -14}),
-
4766 txflags(tfPassive));
-
4767 },
-
4768 [&](Env& env) {
-
4769 BEAST_EXPECT(
-
4770 lp2TSTBalance ==
-
4771 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
-
4772 .asString());
-
4773 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
-
4774 BEAST_EXPECT(lp2TakerGets == offer["taker_gets"].asString());
-
4775 BEAST_EXPECT(
-
4776 lp2TakerPays == offer["taker_pays"]["value"].asString());
-
4777 });
-
4778 }
-
4779
-
4780 void
-
4781 testTradingFee(FeatureBitset features)
-
4782 {
-
4783 testcase("Trading Fee");
-
4784 using namespace jtx;
-
4785
-
4786 // Single Deposit, 1% fee
-
4787 testAMM(
-
4788 [&](AMM& ammAlice, Env& env) {
-
4789 // No fee
-
4790 ammAlice.deposit(carol, USD(3'000));
-
4791 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
-
4792 ammAlice.withdrawAll(carol, USD(3'000));
-
4793 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
4794 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
4795 // Set fee to 1%
-
4796 ammAlice.vote(alice, 1'000);
-
4797 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
-
4798 // Carol gets fewer LPToken ~994, because of the single deposit
-
4799 // fee
-
4800 ammAlice.deposit(carol, USD(3'000));
-
4801 BEAST_EXPECT(ammAlice.expectLPTokens(
-
4802 carol, IOUAmount{994'981155689671, -12}));
-
4803 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
-
4804 // Set fee to 0
-
4805 ammAlice.vote(alice, 0);
-
4806 ammAlice.withdrawAll(carol, USD(0));
-
4807 // Carol gets back less than the original deposit
-
4808 BEAST_EXPECT(expectLine(
-
4809 env,
-
4810 carol,
-
4811 STAmount{USD, UINT64_C(29'994'96220068281), -11}));
-
4812 },
-
4813 {{USD(1'000), EUR(1'000)}},
-
4814 0,
-
4815 std::nullopt,
-
4816 {features});
-
4817
-
4818 // Single deposit with EP not exceeding specified:
-
4819 // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut). 1% fee.
-
4820 testAMM(
-
4821 [&](AMM& ammAlice, Env& env) {
-
4822 auto const balance = env.balance(carol, USD);
-
4823 auto tokensFee = ammAlice.deposit(
-
4824 carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
-
4825 auto const deposit = balance - env.balance(carol, USD);
-
4826 ammAlice.withdrawAll(carol, USD(0));
-
4827 ammAlice.vote(alice, 0);
-
4828 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
4829 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
-
4830 // carol pays ~2008 LPTokens in fees or ~0.5% of the no-fee
-
4831 // LPTokens
-
4832 BEAST_EXPECT(tokensFee == IOUAmount(485'636'0611129, -7));
-
4833 BEAST_EXPECT(tokensNoFee == IOUAmount(487'644'85901109, -8));
-
4834 },
-
4835 std::nullopt,
-
4836 1'000,
-
4837 std::nullopt,
-
4838 {features});
-
4839
-
4840 // Single deposit with EP not exceeding specified:
-
4841 // 200USD with EP not to exceed 0.002020 (AssetIn/TokensOut). 1% fee
-
4842 testAMM(
-
4843 [&](AMM& ammAlice, Env& env) {
-
4844 auto const balance = env.balance(carol, USD);
-
4845 auto const tokensFee = ammAlice.deposit(
-
4846 carol, USD(200), std::nullopt, STAmount{USD, 2020, -6});
-
4847 auto const deposit = balance - env.balance(carol, USD);
-
4848 ammAlice.withdrawAll(carol, USD(0));
-
4849 ammAlice.vote(alice, 0);
-
4850 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
4851 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
-
4852 // carol pays ~475 LPTokens in fees or ~0.5% of the no-fee
-
4853 // LPTokens
-
4854 BEAST_EXPECT(tokensFee == IOUAmount(98'000'00000002, -8));
-
4855 BEAST_EXPECT(tokensNoFee == IOUAmount(98'475'81871545, -8));
-
4856 },
-
4857 std::nullopt,
-
4858 1'000,
-
4859 std::nullopt,
-
4860 {features});
-
4861
-
4862 // Single Withdrawal, 1% fee
-
4863 testAMM(
-
4864 [&](AMM& ammAlice, Env& env) {
-
4865 // No fee
-
4866 ammAlice.deposit(carol, USD(3'000));
-
4867
-
4868 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
-
4869 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
-
4870 // Set fee to 1%
-
4871 ammAlice.vote(alice, 1'000);
-
4872 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
-
4873 // Single withdrawal. Carol gets ~5USD less than deposited.
-
4874 ammAlice.withdrawAll(carol, USD(0));
-
4875 BEAST_EXPECT(expectLine(
-
4876 env,
-
4877 carol,
-
4878 STAmount{USD, UINT64_C(29'994'97487437186), -11}));
-
4879 },
-
4880 {{USD(1'000), EUR(1'000)}},
-
4881 0,
-
4882 std::nullopt,
-
4883 {features});
-
4884
-
4885 // Withdraw with EPrice limit, 1% fee.
-
4886 testAMM(
-
4887 [&](AMM& ammAlice, Env& env) {
-
4888 ammAlice.deposit(carol, 1'000'000);
-
4889 auto const tokensFee = ammAlice.withdraw(
-
4890 carol, USD(100), std::nullopt, IOUAmount{520, 0});
-
4891 // carol withdraws ~1,443.44USD
-
4892 auto const balanceAfterWithdraw = [&]() {
-
4893 if (!features[fixAMMv1_1])
-
4894 return STAmount(USD, UINT64_C(30'443'43891402715), -11);
-
4895 return STAmount(USD, UINT64_C(30'443'43891402714), -11);
-
4896 }();
-
4897 BEAST_EXPECT(env.balance(carol, USD) == balanceAfterWithdraw);
-
4898 // Set to original pool size
-
4899 auto const deposit = balanceAfterWithdraw - USD(29'000);
-
4900 ammAlice.deposit(carol, deposit);
-
4901 // fee 0%
-
4902 ammAlice.vote(alice, 0);
-
4903 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
4904 auto const tokensNoFee = ammAlice.withdraw(carol, deposit);
-
4905 if (!features[fixAMMv1_1])
-
4906 BEAST_EXPECT(
-
4907 env.balance(carol, USD) ==
-
4908 STAmount(USD, UINT64_C(30'443'43891402717), -11));
-
4909 else
-
4910 BEAST_EXPECT(
-
4911 env.balance(carol, USD) ==
-
4912 STAmount(USD, UINT64_C(30'443'43891402716), -11));
-
4913 // carol pays ~4008 LPTokens in fees or ~0.5% of the no-fee
-
4914 // LPTokens
-
4915 if (!features[fixAMMv1_1])
-
4916 BEAST_EXPECT(
-
4917 tokensNoFee == IOUAmount(746'579'80779913, -8));
-
4918 else
-
4919 BEAST_EXPECT(
-
4920 tokensNoFee == IOUAmount(746'579'80779912, -8));
-
4921 BEAST_EXPECT(tokensFee == IOUAmount(750'588'23529411, -8));
-
4922 },
-
4923 std::nullopt,
-
4924 1'000,
-
4925 std::nullopt,
-
4926 {features});
-
4927
-
4928 // Payment, 1% fee
-
4929 testAMM(
-
4930 [&](AMM& ammAlice, Env& env) {
-
4931 fund(
-
4932 env,
-
4933 gw,
-
4934 {bob},
-
4935 XRP(1'000),
-
4936 {USD(1'000), EUR(1'000)},
-
4937 Fund::Acct);
-
4938 // Alice contributed 1010EUR and 1000USD to the pool
-
4939 BEAST_EXPECT(expectLine(env, alice, EUR(28'990)));
-
4940 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
-
4941 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
4942 // Carol pays to Alice with no fee
-
4943 env(pay(carol, alice, EUR(10)),
-
4944 path(~EUR),
-
4945 sendmax(USD(10)),
-
4946 txflags(tfNoRippleDirect));
-
4947 env.close();
-
4948 // Alice has 10EUR more and Carol has 10USD less
-
4949 BEAST_EXPECT(expectLine(env, alice, EUR(29'000)));
-
4950 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
-
4951 BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
-
4952
-
4953 // Set fee to 1%
-
4954 ammAlice.vote(alice, 1'000);
-
4955 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
-
4956 // Bob pays to Carol with 1% fee
-
4957 env(pay(bob, carol, USD(10)),
-
4958 path(~USD),
-
4959 sendmax(EUR(15)),
-
4960 txflags(tfNoRippleDirect));
-
4961 env.close();
-
4962 // Bob sends 10.1~EUR to pay 10USD
-
4963 BEAST_EXPECT(expectLine(
-
4964 env, bob, STAmount{EUR, UINT64_C(989'8989898989899), -13}));
-
4965 // Carol got 10USD
-
4966 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
4967 BEAST_EXPECT(ammAlice.expectBalances(
-
4968 USD(1'000),
-
4969 STAmount{EUR, UINT64_C(1'010'10101010101), -11},
-
4970 ammAlice.tokens()));
-
4971 },
-
4972 {{USD(1'000), EUR(1'010)}},
-
4973 0,
-
4974 std::nullopt,
-
4975 {features});
-
4976
-
4977 // Offer crossing, 0.5% fee
-
4978 testAMM(
-
4979 [&](AMM& ammAlice, Env& env) {
-
4980 // No fee
-
4981 env(offer(carol, EUR(10), USD(10)));
-
4982 env.close();
-
4983 BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
-
4984 BEAST_EXPECT(expectLine(env, carol, EUR(30'010)));
-
4985 // Change pool composition back
-
4986 env(offer(carol, USD(10), EUR(10)));
-
4987 env.close();
-
4988 // Set fee to 0.5%
-
4989 ammAlice.vote(alice, 500);
-
4990 BEAST_EXPECT(ammAlice.expectTradingFee(500));
-
4991 env(offer(carol, EUR(10), USD(10)));
-
4992 env.close();
-
4993 // Alice gets fewer ~4.97EUR for ~5.02USD, the difference goes
-
4994 // to the pool
-
4995 BEAST_EXPECT(expectLine(
-
4996 env,
-
4997 carol,
-
4998 STAmount{USD, UINT64_C(29'995'02512562814), -11}));
-
4999 BEAST_EXPECT(expectLine(
-
5000 env,
-
5001 carol,
-
5002 STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
-
5003 BEAST_EXPECT(expectOffers(
-
5004 env,
-
5005 carol,
-
5006 1,
-
5007 {{Amounts{
-
5008 STAmount{EUR, UINT64_C(5'025125628140703), -15},
-
5009 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
-
5010 if (!features[fixAMMv1_1])
-
5011 {
-
5012 BEAST_EXPECT(ammAlice.expectBalances(
-
5013 STAmount{USD, UINT64_C(1'004'974874371859), -12},
-
5014 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
-
5015 ammAlice.tokens()));
-
5016 }
-
5017 else
-
5018 {
-
5019 BEAST_EXPECT(ammAlice.expectBalances(
-
5020 STAmount{USD, UINT64_C(1'004'97487437186), -11},
-
5021 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
-
5022 ammAlice.tokens()));
-
5023 }
-
5024 },
-
5025 {{USD(1'000), EUR(1'010)}},
-
5026 0,
-
5027 std::nullopt,
-
5028 {features});
-
5029
-
5030 // Payment with AMM and CLOB offer, 0 fee
-
5031 // AMM liquidity is consumed first up to CLOB offer quality
-
5032 // CLOB offer is fully consumed next
-
5033 // Remaining amount is consumed via AMM liquidity
-
5034 {
-
5035 Env env(*this, features);
-
5036 Account const ed("ed");
-
5037 fund(
-
5038 env,
-
5039 gw,
-
5040 {alice, bob, carol, ed},
-
5041 XRP(1'000),
-
5042 {USD(2'000), EUR(2'000)});
-
5043 env(offer(carol, EUR(5), USD(5)));
-
5044 AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
-
5045 env(pay(bob, ed, USD(10)),
-
5046 path(~USD),
-
5047 sendmax(EUR(15)),
-
5048 txflags(tfNoRippleDirect));
-
5049 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
-
5050 if (!features[fixAMMv1_1])
-
5051 {
-
5052 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
-
5053 BEAST_EXPECT(ammAlice.expectBalances(
-
5054 USD(1'000), EUR(1'005), ammAlice.tokens()));
-
5055 }
-
5056 else
-
5057 {
-
5058 BEAST_EXPECT(expectLine(
-
5059 env, bob, STAmount(EUR, UINT64_C(1989'999999999999), -12)));
-
5060 BEAST_EXPECT(ammAlice.expectBalances(
-
5061 USD(1'000),
-
5062 STAmount(EUR, UINT64_C(1005'000000000001), -12),
-
5063 ammAlice.tokens()));
-
5064 }
-
5065 BEAST_EXPECT(expectOffers(env, carol, 0));
-
5066 }
-
5067
-
5068 // Payment with AMM and CLOB offer. Same as above but with 0.25%
-
5069 // fee.
-
5070 {
-
5071 Env env(*this, features);
-
5072 Account const ed("ed");
-
5073 fund(
-
5074 env,
-
5075 gw,
-
5076 {alice, bob, carol, ed},
-
5077 XRP(1'000),
-
5078 {USD(2'000), EUR(2'000)});
-
5079 env(offer(carol, EUR(5), USD(5)));
-
5080 // Set 0.25% fee
-
5081 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 250);
-
5082 env(pay(bob, ed, USD(10)),
-
5083 path(~USD),
-
5084 sendmax(EUR(15)),
-
5085 txflags(tfNoRippleDirect));
-
5086 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
-
5087 if (!features[fixAMMv1_1])
-
5088 {
-
5089 BEAST_EXPECT(expectLine(
-
5090 env,
-
5091 bob,
-
5092 STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
-
5093 BEAST_EXPECT(ammAlice.expectBalances(
-
5094 USD(1'000),
-
5095 STAmount{EUR, UINT64_C(1'005'012546992382), -12},
-
5096 ammAlice.tokens()));
-
5097 }
-
5098 else
-
5099 {
-
5100 BEAST_EXPECT(expectLine(
-
5101 env,
-
5102 bob,
-
5103 STAmount{EUR, UINT64_C(1'989'987453007628), -12}));
-
5104 BEAST_EXPECT(ammAlice.expectBalances(
-
5105 USD(1'000),
-
5106 STAmount{EUR, UINT64_C(1'005'012546992372), -12},
-
5107 ammAlice.tokens()));
-
5108 }
-
5109 BEAST_EXPECT(expectOffers(env, carol, 0));
-
5110 }
-
5111
-
5112 // Payment with AMM and CLOB offer. AMM has a better
-
5113 // spot price quality, but 1% fee offsets that. As the result
-
5114 // the entire trade is executed via LOB.
-
5115 {
-
5116 Env env(*this, features);
-
5117 Account const ed("ed");
-
5118 fund(
-
5119 env,
-
5120 gw,
-
5121 {alice, bob, carol, ed},
-
5122 XRP(1'000),
-
5123 {USD(2'000), EUR(2'000)});
-
5124 env(offer(carol, EUR(10), USD(10)));
-
5125 // Set 1% fee
-
5126 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
-
5127 env(pay(bob, ed, USD(10)),
-
5128 path(~USD),
-
5129 sendmax(EUR(15)),
-
5130 txflags(tfNoRippleDirect));
-
5131 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
-
5132 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
-
5133 BEAST_EXPECT(ammAlice.expectBalances(
-
5134 USD(1'005), EUR(1'000), ammAlice.tokens()));
-
5135 BEAST_EXPECT(expectOffers(env, carol, 0));
-
5136 }
-
5137
-
5138 // Payment with AMM and CLOB offer. AMM has a better
-
5139 // spot price quality, but 1% fee offsets that.
-
5140 // The CLOB offer is consumed first and the remaining
-
5141 // amount is consumed via AMM liquidity.
-
5142 {
-
5143 Env env(*this, features);
-
5144 Account const ed("ed");
-
5145 fund(
-
5146 env,
-
5147 gw,
-
5148 {alice, bob, carol, ed},
-
5149 XRP(1'000),
-
5150 {USD(2'000), EUR(2'000)});
-
5151 env(offer(carol, EUR(9), USD(9)));
-
5152 // Set 1% fee
-
5153 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
-
5154 env(pay(bob, ed, USD(10)),
-
5155 path(~USD),
-
5156 sendmax(EUR(15)),
-
5157 txflags(tfNoRippleDirect));
-
5158 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
-
5159 BEAST_EXPECT(expectLine(
-
5160 env, bob, STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
-
5161 BEAST_EXPECT(ammAlice.expectBalances(
-
5162 USD(1'004),
-
5163 STAmount{EUR, UINT64_C(1'001'006076703288), -12},
-
5164 ammAlice.tokens()));
-
5165 BEAST_EXPECT(expectOffers(env, carol, 0));
-
5166 }
-
5167 }
-
5168
-
5169 void
-
5170 testAdjustedTokens(FeatureBitset features)
-
5171 {
-
5172 testcase("Adjusted Deposit/Withdraw Tokens");
-
5173
-
5174 using namespace jtx;
-
5175
-
5176 // Deposit/Withdraw in USD
-
5177 testAMM(
-
5178 [&](AMM& ammAlice, Env& env) {
-
5179 Account const bob("bob");
-
5180 Account const ed("ed");
-
5181 Account const paul("paul");
-
5182 Account const dan("dan");
-
5183 Account const chris("chris");
-
5184 Account const simon("simon");
-
5185 Account const ben("ben");
-
5186 Account const nataly("nataly");
-
5187 fund(
-
5188 env,
-
5189 gw,
-
5190 {bob, ed, paul, dan, chris, simon, ben, nataly},
-
5191 {USD(1'500'000)},
-
5192 Fund::Acct);
-
5193 for (int i = 0; i < 10; ++i)
-
5194 {
-
5195 ammAlice.deposit(ben, STAmount{USD, 1, -10});
-
5196 ammAlice.withdrawAll(ben, USD(0));
-
5197 ammAlice.deposit(simon, USD(0.1));
-
5198 ammAlice.withdrawAll(simon, USD(0));
-
5199 ammAlice.deposit(chris, USD(1));
-
5200 ammAlice.withdrawAll(chris, USD(0));
-
5201 ammAlice.deposit(dan, USD(10));
-
5202 ammAlice.withdrawAll(dan, USD(0));
-
5203 ammAlice.deposit(bob, USD(100));
-
5204 ammAlice.withdrawAll(bob, USD(0));
-
5205 ammAlice.deposit(carol, USD(1'000));
-
5206 ammAlice.withdrawAll(carol, USD(0));
-
5207 ammAlice.deposit(ed, USD(10'000));
-
5208 ammAlice.withdrawAll(ed, USD(0));
-
5209 ammAlice.deposit(paul, USD(100'000));
-
5210 ammAlice.withdrawAll(paul, USD(0));
-
5211 ammAlice.deposit(nataly, USD(1'000'000));
-
5212 ammAlice.withdrawAll(nataly, USD(0));
-
5213 }
-
5214 // Due to round off some accounts have a tiny gain, while
-
5215 // other have a tiny loss. The last account to withdraw
-
5216 // gets everything in the pool.
-
5217 if (!features[fixAMMv1_1])
-
5218 BEAST_EXPECT(ammAlice.expectBalances(
-
5219 XRP(10'000),
-
5220 STAmount{USD, UINT64_C(10'000'0000000013), -10},
-
5221 IOUAmount{10'000'000}));
-
5222 else
-
5223 BEAST_EXPECT(ammAlice.expectBalances(
-
5224 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
-
5225 BEAST_EXPECT(expectLine(env, ben, USD(1'500'000)));
-
5226 BEAST_EXPECT(expectLine(env, simon, USD(1'500'000)));
-
5227 BEAST_EXPECT(expectLine(env, chris, USD(1'500'000)));
-
5228 BEAST_EXPECT(expectLine(env, dan, USD(1'500'000)));
-
5229 if (!features[fixAMMv1_1])
-
5230 BEAST_EXPECT(expectLine(
-
5231 env,
-
5232 carol,
-
5233 STAmount{USD, UINT64_C(30'000'00000000001), -11}));
-
5234 else
-
5235 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
5236 BEAST_EXPECT(expectLine(env, ed, USD(1'500'000)));
-
5237 BEAST_EXPECT(expectLine(env, paul, USD(1'500'000)));
-
5238 if (!features[fixAMMv1_1])
-
5239 BEAST_EXPECT(expectLine(
-
5240 env,
-
5241 nataly,
-
5242 STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
-
5243 else
-
5244 BEAST_EXPECT(expectLine(
-
5245 env,
-
5246 nataly,
-
5247 STAmount{USD, UINT64_C(1'500'000'000000005), -9}));
-
5248 ammAlice.withdrawAll(alice);
-
5249 BEAST_EXPECT(!ammAlice.ammExists());
-
5250 if (!features[fixAMMv1_1])
-
5251 BEAST_EXPECT(expectLine(
-
5252 env,
-
5253 alice,
-
5254 STAmount{USD, UINT64_C(30'000'0000000013), -10}));
-
5255 else
-
5256 BEAST_EXPECT(expectLine(env, alice, USD(30'000)));
-
5257 // alice XRP balance is 30,000initial - 50 ammcreate fee -
-
5258 // 10drops fee
-
5259 BEAST_EXPECT(
-
5260 accountBalance(env, alice) ==
-
5261 std::to_string(
-
5262 29950000000 - env.current()->fees().base.drops()));
-
5263 },
-
5264 std::nullopt,
-
5265 0,
-
5266 std::nullopt,
-
5267 {features});
-
5268
-
5269 // Same as above but deposit/withdraw in XRP
-
5270 testAMM([&](AMM& ammAlice, Env& env) {
-
5271 Account const bob("bob");
-
5272 Account const ed("ed");
-
5273 Account const paul("paul");
-
5274 Account const dan("dan");
-
5275 Account const chris("chris");
-
5276 Account const simon("simon");
-
5277 Account const ben("ben");
-
5278 Account const nataly("nataly");
-
5279 fund(
-
5280 env,
-
5281 gw,
-
5282 {bob, ed, paul, dan, chris, simon, ben, nataly},
-
5283 XRP(2'000'000),
-
5284 {},
-
5285 Fund::Acct);
-
5286 for (int i = 0; i < 10; ++i)
-
5287 {
-
5288 ammAlice.deposit(ben, XRPAmount{1});
-
5289 ammAlice.withdrawAll(ben, XRP(0));
-
5290 ammAlice.deposit(simon, XRPAmount(1'000));
-
5291 ammAlice.withdrawAll(simon, XRP(0));
-
5292 ammAlice.deposit(chris, XRP(1));
-
5293 ammAlice.withdrawAll(chris, XRP(0));
-
5294 ammAlice.deposit(dan, XRP(10));
-
5295 ammAlice.withdrawAll(dan, XRP(0));
-
5296 ammAlice.deposit(bob, XRP(100));
-
5297 ammAlice.withdrawAll(bob, XRP(0));
-
5298 ammAlice.deposit(carol, XRP(1'000));
-
5299 ammAlice.withdrawAll(carol, XRP(0));
-
5300 ammAlice.deposit(ed, XRP(10'000));
-
5301 ammAlice.withdrawAll(ed, XRP(0));
-
5302 ammAlice.deposit(paul, XRP(100'000));
-
5303 ammAlice.withdrawAll(paul, XRP(0));
-
5304 ammAlice.deposit(nataly, XRP(1'000'000));
-
5305 ammAlice.withdrawAll(nataly, XRP(0));
-
5306 }
-
5307 // No round off with XRP in this test
-
5308 BEAST_EXPECT(ammAlice.expectBalances(
-
5309 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
-
5310 ammAlice.withdrawAll(alice);
-
5311 BEAST_EXPECT(!ammAlice.ammExists());
-
5312 // 20,000 initial - (deposit+withdraw) * 10
-
5313 auto const xrpBalance = (XRP(2'000'000) - txfee(env, 20)).getText();
-
5314 BEAST_EXPECT(accountBalance(env, ben) == xrpBalance);
-
5315 BEAST_EXPECT(accountBalance(env, simon) == xrpBalance);
-
5316 BEAST_EXPECT(accountBalance(env, chris) == xrpBalance);
-
5317 BEAST_EXPECT(accountBalance(env, dan) == xrpBalance);
-
5318
-
5319 auto const baseFee = env.current()->fees().base.drops();
-
5320 // 30,000 initial - (deposit+withdraw) * 10
-
5321 BEAST_EXPECT(
-
5322 accountBalance(env, carol) ==
-
5323 std::to_string(30000000000 - 20 * baseFee));
-
5324 BEAST_EXPECT(accountBalance(env, ed) == xrpBalance);
-
5325 BEAST_EXPECT(accountBalance(env, paul) == xrpBalance);
-
5326 BEAST_EXPECT(accountBalance(env, nataly) == xrpBalance);
-
5327 // 30,000 initial - 50 ammcreate fee - 10drops withdraw fee
-
5328 BEAST_EXPECT(
-
5329 accountBalance(env, alice) ==
-
5330 std::to_string(29950000000 - baseFee));
-
5331 });
-
5332 }
-
5333
-
5334 void
-
5335 testAutoDelete()
-
5336 {
-
5337 testcase("Auto Delete");
-
5338
-
5339 using namespace jtx;
-
5340 FeatureBitset const all{supported_amendments()};
-
5341
-
5342 {
-
5343 Env env(
-
5344 *this,
-
5345 envconfig([](std::unique_ptr<Config> cfg) {
-
5346 cfg->FEES.reference_fee = XRPAmount(1);
-
5347 return cfg;
-
5348 }),
-
5349 all);
-
5350 fund(env, gw, {alice}, XRP(20'000), {USD(10'000)});
-
5351 AMM amm(env, gw, XRP(10'000), USD(10'000));
-
5352 for (auto i = 0; i < maxDeletableAMMTrustLines + 10; ++i)
-
5353 {
-
5354 Account const a{std::to_string(i)};
-
5355 env.fund(XRP(1'000), a);
-
5356 env(trust(a, STAmount{amm.lptIssue(), 10'000}));
-
5357 env.close();
-
5358 }
-
5359 // The trustlines are partially deleted,
-
5360 // AMM is set to an empty state.
-
5361 amm.withdrawAll(gw);
-
5362 BEAST_EXPECT(amm.ammExists());
-
5363
-
5364 // Bid,Vote,Deposit,Withdraw,SetTrust failing with
-
5365 // tecAMM_EMPTY. Deposit succeeds with tfTwoAssetIfEmpty option.
-
5366 env(amm.bid({
-
5367 .account = alice,
-
5368 .bidMin = 1000,
-
5369 }),
-
5370 ter(tecAMM_EMPTY));
-
5371 amm.vote(
-
5372 std::nullopt,
-
5373 100,
-
5374 std::nullopt,
-
5375 std::nullopt,
-
5376 std::nullopt,
-
5377 ter(tecAMM_EMPTY));
-
5378 amm.withdraw(
-
5379 alice, 100, std::nullopt, std::nullopt, ter(tecAMM_EMPTY));
-
5380 amm.deposit(
-
5381 alice,
-
5382 USD(100),
-
5383 std::nullopt,
-
5384 std::nullopt,
-
5385 std::nullopt,
-
5386 ter(tecAMM_EMPTY));
-
5387 env(trust(alice, STAmount{amm.lptIssue(), 10'000}),
-
5388 ter(tecAMM_EMPTY));
-
5389
-
5390 // Can deposit with tfTwoAssetIfEmpty option
-
5391 amm.deposit(
-
5392 alice,
-
5393 std::nullopt,
-
5394 XRP(10'000),
-
5395 USD(10'000),
-
5396 std::nullopt,
-
5397 tfTwoAssetIfEmpty,
-
5398 std::nullopt,
-
5399 std::nullopt,
-
5400 1'000);
-
5401 BEAST_EXPECT(
-
5402 amm.expectBalances(XRP(10'000), USD(10'000), amm.tokens()));
-
5403 BEAST_EXPECT(amm.expectTradingFee(1'000));
-
5404 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
-
5405
-
5406 // Withdrawing all tokens deletes AMM since the number
-
5407 // of remaining trustlines is less than max
-
5408 amm.withdrawAll(alice);
-
5409 BEAST_EXPECT(!amm.ammExists());
-
5410 BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount())));
-
5411 }
-
5412
-
5413 {
-
5414 Env env(
-
5415 *this,
-
5416 envconfig([](std::unique_ptr<Config> cfg) {
-
5417 cfg->FEES.reference_fee = XRPAmount(1);
-
5418 return cfg;
-
5419 }),
-
5420 all);
-
5421 fund(env, gw, {alice}, XRP(20'000), {USD(10'000)});
-
5422 AMM amm(env, gw, XRP(10'000), USD(10'000));
-
5423 for (auto i = 0; i < maxDeletableAMMTrustLines * 2 + 10; ++i)
-
5424 {
-
5425 Account const a{std::to_string(i)};
-
5426 env.fund(XRP(1'000), a);
-
5427 env(trust(a, STAmount{amm.lptIssue(), 10'000}));
-
5428 env.close();
-
5429 }
-
5430 // The trustlines are partially deleted.
-
5431 amm.withdrawAll(gw);
-
5432 BEAST_EXPECT(amm.ammExists());
-
5433
-
5434 // AMMDelete has to be called twice to delete AMM.
-
5435 amm.ammDelete(alice, ter(tecINCOMPLETE));
-
5436 BEAST_EXPECT(amm.ammExists());
-
5437 // Deletes remaining trustlines and deletes AMM.
-
5438 amm.ammDelete(alice);
-
5439 BEAST_EXPECT(!amm.ammExists());
-
5440 BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount())));
-
5441
-
5442 // Try redundant delete
-
5443 amm.ammDelete(alice, ter(terNO_AMM));
-
5444 }
-
5445 }
-
5446
-
5447 void
-
5448 testClawback()
-
5449 {
-
5450 testcase("Clawback");
-
5451 using namespace jtx;
-
5452 Env env(*this);
-
5453 env.fund(XRP(2'000), gw);
-
5454 env.fund(XRP(2'000), alice);
-
5455 AMM amm(env, gw, XRP(1'000), USD(1'000));
-
5456 env(fset(gw, asfAllowTrustLineClawback), ter(tecOWNERS));
-
5457 }
-
5458
-
5459 void
-
5460 testAMMID()
-
5461 {
-
5462 testcase("AMMID");
-
5463 using namespace jtx;
-
5464 testAMM([&](AMM& amm, Env& env) {
-
5465 amm.setClose(false);
-
5466 auto const info = env.rpc(
-
5467 "json",
-
5468 "account_info",
-
5469 std::string(
-
5470 "{\"account\": \"" + to_string(amm.ammAccount()) + "\"}"));
-
5471 try
-
5472 {
-
5473 BEAST_EXPECT(
-
5474 info[jss::result][jss::account_data][jss::AMMID]
-
5475 .asString() == to_string(amm.ammID()));
-
5476 }
-
5477 catch (...)
-
5478 {
-
5479 fail();
-
5480 }
-
5481 amm.deposit(carol, 1'000);
-
5482 auto affected = env.meta()->getJson(
-
5483 JsonOptions::none)[sfAffectedNodes.fieldName];
-
5484 try
-
5485 {
-
5486 bool found = false;
-
5487 for (auto const& node : affected)
-
5488 {
-
5489 if (node.isMember(sfModifiedNode.fieldName) &&
-
5490 node[sfModifiedNode.fieldName]
-
5491 [sfLedgerEntryType.fieldName]
-
5492 .asString() == "AccountRoot" &&
-
5493 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
-
5494 [jss::Account]
-
5495 .asString() == to_string(amm.ammAccount()))
-
5496 {
-
5497 found = node[sfModifiedNode.fieldName]
-
5498 [sfFinalFields.fieldName][jss::AMMID]
-
5499 .asString() == to_string(amm.ammID());
-
5500 break;
-
5501 }
-
5502 }
-
5503 BEAST_EXPECT(found);
-
5504 }
-
5505 catch (...)
-
5506 {
-
5507 fail();
-
5508 }
-
5509 });
-
5510 }
-
5511
-
5512 void
-
5513 testSelection(FeatureBitset features)
-
5514 {
-
5515 testcase("Offer/Strand Selection");
-
5516 using namespace jtx;
-
5517 Account const ed("ed");
-
5518 Account const gw1("gw1");
-
5519 auto const ETH = gw1["ETH"];
-
5520 auto const CAN = gw1["CAN"];
-
5521
-
5522 // These tests are expected to fail if the OwnerPaysFee feature
-
5523 // is ever supported. Updates will need to be made to AMM handling
-
5524 // in the payment engine, and these tests will need to be updated.
-
5525
-
5526 auto prep = [&](Env& env, auto gwRate, auto gw1Rate) {
-
5527 fund(env, gw, {alice, carol, bob, ed}, XRP(2'000), {USD(2'000)});
-
5528 env.fund(XRP(2'000), gw1);
-
5529 fund(
-
5530 env,
-
5531 gw1,
-
5532 {alice, carol, bob, ed},
-
5533 {ETH(2'000), CAN(2'000)},
-
5534 Fund::IOUOnly);
-
5535 env(rate(gw, gwRate));
-
5536 env(rate(gw1, gw1Rate));
-
5537 env.close();
-
5538 };
-
5539
-
5540 for (auto const& rates :
-
5541 {std::make_pair(1.5, 1.9), std::make_pair(1.9, 1.5)})
-
5542 {
-
5543 // Offer Selection
-
5544
-
5545 // Cross-currency payment: AMM has the same spot price quality
-
5546 // as CLOB's offer and can't generate a better quality offer.
-
5547 // The transfer fee in this case doesn't change the CLOB quality
-
5548 // because trIn is ignored on adjustment and trOut on payment is
-
5549 // also ignored because ownerPaysTransferFee is false in this
-
5550 // case. Run test for 0) offer, 1) AMM, 2) offer and AMM to
-
5551 // verify that the quality is better in the first case, and CLOB
-
5552 // is selected in the second case.
-
5553 {
-
5554 std::array<Quality, 3> q;
-
5555 for (auto i = 0; i < 3; ++i)
-
5556 {
-
5557 Env env(*this, features);
-
5558 prep(env, rates.first, rates.second);
-
5559 std::optional<AMM> amm;
-
5560 if (i == 0 || i == 2)
-
5561 {
-
5562 env(offer(ed, ETH(400), USD(400)), txflags(tfPassive));
-
5563 env.close();
-
5564 }
-
5565 if (i > 0)
-
5566 amm.emplace(env, ed, USD(1'000), ETH(1'000));
-
5567 env(pay(carol, bob, USD(100)),
-
5568 path(~USD),
-
5569 sendmax(ETH(500)));
-
5570 env.close();
-
5571 // CLOB and AMM, AMM is not selected
-
5572 if (i == 2)
-
5573 {
-
5574 BEAST_EXPECT(amm->expectBalances(
-
5575 USD(1'000), ETH(1'000), amm->tokens()));
-
5576 }
-
5577 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
-
5578 q[i] = Quality(Amounts{
-
5579 ETH(2'000) - env.balance(carol, ETH),
-
5580 env.balance(bob, USD) - USD(2'000)});
-
5581 }
-
5582 // CLOB is better quality than AMM
-
5583 BEAST_EXPECT(q[0] > q[1]);
-
5584 // AMM is not selected with CLOB
-
5585 BEAST_EXPECT(q[0] == q[2]);
-
5586 }
-
5587 // Offer crossing: AMM has the same spot price quality
-
5588 // as CLOB's offer and can't generate a better quality offer.
-
5589 // The transfer fee in this case doesn't change the CLOB quality
-
5590 // because the quality adjustment is ignored for the offer
-
5591 // crossing.
-
5592 for (auto i = 0; i < 3; ++i)
-
5593 {
-
5594 Env env(*this, features);
-
5595 prep(env, rates.first, rates.second);
-
5596 std::optional<AMM> amm;
-
5597 if (i == 0 || i == 2)
-
5598 {
-
5599 env(offer(ed, ETH(400), USD(400)), txflags(tfPassive));
-
5600 env.close();
-
5601 }
-
5602 if (i > 0)
-
5603 amm.emplace(env, ed, USD(1'000), ETH(1'000));
-
5604 env(offer(alice, USD(400), ETH(400)));
-
5605 env.close();
-
5606 // AMM is not selected
-
5607 if (i > 0)
-
5608 {
-
5609 BEAST_EXPECT(amm->expectBalances(
-
5610 USD(1'000), ETH(1'000), amm->tokens()));
-
5611 }
-
5612 if (i == 0 || i == 2)
-
5613 {
-
5614 // Fully crosses
-
5615 BEAST_EXPECT(expectOffers(env, alice, 0));
-
5616 }
-
5617 // Fails to cross because AMM is not selected
-
5618 else
-
5619 {
-
5620 BEAST_EXPECT(expectOffers(
-
5621 env, alice, 1, {Amounts{USD(400), ETH(400)}}));
-
5622 }
-
5623 BEAST_EXPECT(expectOffers(env, ed, 0));
-
5624 }
-
5625
-
5626 // Show that the CLOB quality reduction
-
5627 // results in AMM offer selection.
-
5628
-
5629 // Same as the payment but reduced offer quality
-
5630 {
-
5631 std::array<Quality, 3> q;
-
5632 for (auto i = 0; i < 3; ++i)
-
5633 {
-
5634 Env env(*this, features);
-
5635 prep(env, rates.first, rates.second);
-
5636 std::optional<AMM> amm;
-
5637 if (i == 0 || i == 2)
-
5638 {
-
5639 env(offer(ed, ETH(400), USD(300)), txflags(tfPassive));
-
5640 env.close();
-
5641 }
-
5642 if (i > 0)
-
5643 amm.emplace(env, ed, USD(1'000), ETH(1'000));
-
5644 env(pay(carol, bob, USD(100)),
-
5645 path(~USD),
-
5646 sendmax(ETH(500)));
-
5647 env.close();
-
5648 // AMM and CLOB are selected
-
5649 if (i > 0)
-
5650 {
-
5651 BEAST_EXPECT(!amm->expectBalances(
-
5652 USD(1'000), ETH(1'000), amm->tokens()));
-
5653 }
-
5654 if (i == 2 && !features[fixAMMv1_1])
-
5655 {
-
5656 if (rates.first == 1.5)
-
5657 {
-
5658 if (!features[fixAMMv1_1])
-
5659 BEAST_EXPECT(expectOffers(
-
5660 env,
-
5661 ed,
-
5662 1,
-
5663 {{Amounts{
-
5664 STAmount{
-
5665 ETH,
-
5666 UINT64_C(378'6327949540823),
-
5667 -13},
-
5668 STAmount{
-
5669 USD,
-
5670 UINT64_C(283'9745962155617),
-
5671 -13}}}}));
-
5672 else
-
5673 BEAST_EXPECT(expectOffers(
-
5674 env,
-
5675 ed,
-
5676 1,
-
5677 {{Amounts{
-
5678 STAmount{
-
5679 ETH,
-
5680 UINT64_C(378'6327949540813),
-
5681 -13},
-
5682 STAmount{
-
5683 USD,
-
5684 UINT64_C(283'974596215561),
-
5685 -12}}}}));
-
5686 }
-
5687 else
-
5688 {
-
5689 if (!features[fixAMMv1_1])
-
5690 BEAST_EXPECT(expectOffers(
-
5691 env,
-
5692 ed,
-
5693 1,
-
5694 {{Amounts{
-
5695 STAmount{
-
5696 ETH,
-
5697 UINT64_C(325'299461620749),
-
5698 -12},
-
5699 STAmount{
-
5700 USD,
-
5701 UINT64_C(243'9745962155617),
-
5702 -13}}}}));
-
5703 else
-
5704 BEAST_EXPECT(expectOffers(
-
5705 env,
-
5706 ed,
-
5707 1,
-
5708 {{Amounts{
-
5709 STAmount{
-
5710 ETH,
-
5711 UINT64_C(325'299461620748),
-
5712 -12},
-
5713 STAmount{
-
5714 USD,
-
5715 UINT64_C(243'974596215561),
-
5716 -12}}}}));
-
5717 }
-
5718 }
-
5719 else if (i == 2)
-
5720 {
-
5721 if (rates.first == 1.5)
-
5722 {
-
5723 BEAST_EXPECT(expectOffers(
-
5724 env,
-
5725 ed,
-
5726 1,
-
5727 {{Amounts{
-
5728 STAmount{
-
5729 ETH, UINT64_C(378'6327949540812), -13},
-
5730 STAmount{
-
5731 USD,
-
5732 UINT64_C(283'9745962155609),
-
5733 -13}}}}));
-
5734 }
-
5735 else
-
5736 {
-
5737 BEAST_EXPECT(expectOffers(
-
5738 env,
-
5739 ed,
-
5740 1,
-
5741 {{Amounts{
-
5742 STAmount{
-
5743 ETH, UINT64_C(325'2994616207479), -13},
-
5744 STAmount{
-
5745 USD,
-
5746 UINT64_C(243'9745962155609),
-
5747 -13}}}}));
-
5748 }
-
5749 }
-
5750 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
-
5751 q[i] = Quality(Amounts{
-
5752 ETH(2'000) - env.balance(carol, ETH),
-
5753 env.balance(bob, USD) - USD(2'000)});
-
5754 }
-
5755 // AMM is better quality
-
5756 BEAST_EXPECT(q[1] > q[0]);
-
5757 // AMM and CLOB produce better quality
-
5758 BEAST_EXPECT(q[2] > q[1]);
-
5759 }
-
5760
-
5761 // Same as the offer-crossing but reduced offer quality
-
5762 for (auto i = 0; i < 3; ++i)
-
5763 {
-
5764 Env env(*this, features);
-
5765 prep(env, rates.first, rates.second);
-
5766 std::optional<AMM> amm;
-
5767 if (i == 0 || i == 2)
-
5768 {
-
5769 env(offer(ed, ETH(400), USD(250)), txflags(tfPassive));
-
5770 env.close();
-
5771 }
-
5772 if (i > 0)
-
5773 amm.emplace(env, ed, USD(1'000), ETH(1'000));
-
5774 env(offer(alice, USD(250), ETH(400)));
-
5775 env.close();
-
5776 // AMM is selected in both cases
-
5777 if (i > 0)
-
5778 {
-
5779 BEAST_EXPECT(!amm->expectBalances(
-
5780 USD(1'000), ETH(1'000), amm->tokens()));
-
5781 }
-
5782 // Partially crosses, AMM is selected, CLOB fails
-
5783 // limitQuality
-
5784 if (i == 2)
-
5785 {
-
5786 if (rates.first == 1.5)
-
5787 {
-
5788 if (!features[fixAMMv1_1])
-
5789 {
-
5790 BEAST_EXPECT(expectOffers(
-
5791 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
-
5792 BEAST_EXPECT(expectOffers(
-
5793 env,
-
5794 alice,
-
5795 1,
-
5796 {{Amounts{
-
5797 STAmount{
-
5798 USD, UINT64_C(40'5694150420947), -13},
-
5799 STAmount{
-
5800 ETH, UINT64_C(64'91106406735152), -14},
-
5801 }}}));
-
5802 }
-
5803 else
-
5804 {
-
5805 // Ed offer is partially crossed.
-
5806 // The updated rounding makes limitQuality
-
5807 // work if both amendments are enabled
-
5808 BEAST_EXPECT(expectOffers(
-
5809 env,
-
5810 ed,
-
5811 1,
-
5812 {{Amounts{
-
5813 STAmount{
-
5814 ETH, UINT64_C(335'0889359326475), -13},
-
5815 STAmount{
-
5816 USD, UINT64_C(209'4305849579047), -13},
-
5817 }}}));
-
5818 BEAST_EXPECT(expectOffers(env, alice, 0));
-
5819 }
-
5820 }
-
5821 else
-
5822 {
-
5823 if (!features[fixAMMv1_1])
-
5824 {
-
5825 // Ed offer is partially crossed.
-
5826 BEAST_EXPECT(expectOffers(
-
5827 env,
-
5828 ed,
-
5829 1,
-
5830 {{Amounts{
-
5831 STAmount{
-
5832 ETH, UINT64_C(335'0889359326485), -13},
-
5833 STAmount{
-
5834 USD, UINT64_C(209'4305849579053), -13},
-
5835 }}}));
-
5836 BEAST_EXPECT(expectOffers(env, alice, 0));
-
5837 }
-
5838 else
-
5839 {
-
5840 // Ed offer is partially crossed.
-
5841 BEAST_EXPECT(expectOffers(
-
5842 env,
-
5843 ed,
-
5844 1,
-
5845 {{Amounts{
-
5846 STAmount{
-
5847 ETH, UINT64_C(335'0889359326475), -13},
-
5848 STAmount{
-
5849 USD, UINT64_C(209'4305849579047), -13},
-
5850 }}}));
-
5851 BEAST_EXPECT(expectOffers(env, alice, 0));
-
5852 }
-
5853 }
-
5854 }
-
5855 }
-
5856
-
5857 // Strand selection
-
5858
-
5859 // Two book steps strand quality is 1.
-
5860 // AMM strand's best quality is equal to AMM's spot price
-
5861 // quality, which is 1. Both strands (steps) are adjusted
-
5862 // for the transfer fee in qualityUpperBound. In case
-
5863 // of two strands, AMM offers have better quality and are
-
5864 // consumed first, remaining liquidity is generated by CLOB
-
5865 // offers. Liquidity from two strands is better in this case
-
5866 // than in case of one strand with two book steps. Liquidity
-
5867 // from one strand with AMM has better quality than either one
-
5868 // strand with two book steps or two strands. It may appear
-
5869 // unintuitive, but one strand with AMM is optimized and
-
5870 // generates one AMM offer, while in case of two strands,
-
5871 // multiple AMM offers are generated, which results in slightly
-
5872 // worse overall quality.
-
5873 {
-
5874 std::array<Quality, 3> q;
-
5875 for (auto i = 0; i < 3; ++i)
-
5876 {
-
5877 Env env(*this, features);
-
5878 prep(env, rates.first, rates.second);
-
5879 std::optional<AMM> amm;
-
5880
-
5881 if (i == 0 || i == 2)
+
4724 // Individually frozen account
+
4725 testAMM(
+
4726 [&](AMM& ammAlice, Env& env) {
+
4727 env(trust(gw, carol["USD"](0), tfSetFreeze));
+
4728 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
4729 env.close();
+
4730 env(pay(alice, carol, USD(1)),
+
4731 path(~USD),
+
4732 sendmax(XRP(10)),
+
4733 txflags(tfNoRippleDirect | tfPartialPayment),
+
4734 ter(tesSUCCESS));
+
4735 },
+
4736 std::nullopt,
+
4737 0,
+
4738 std::nullopt,
+
4739 {features});
+
4740 }
+
4741
+
4742 void
+
4743 testAMMTokens()
+
4744 {
+
4745 testcase("AMM Tokens");
+
4746 using namespace jtx;
+
4747
+
4748 // Offer crossing with AMM LPTokens and XRP.
+
4749 testAMM([&](AMM& ammAlice, Env& env) {
+
4750 auto const baseFee = env.current()->fees().base.drops();
+
4751 auto const token1 = ammAlice.lptIssue();
+
4752 auto priceXRP = ammAssetOut(
+
4753 STAmount{XRPAmount{10'000'000'000}},
+
4754 STAmount{token1, 10'000'000},
+
4755 STAmount{token1, 5'000'000},
+
4756 0);
+
4757 // Carol places an order to buy LPTokens
+
4758 env(offer(carol, STAmount{token1, 5'000'000}, priceXRP));
+
4759 // Alice places an order to sell LPTokens
+
4760 env(offer(alice, priceXRP, STAmount{token1, 5'000'000}));
+
4761 // Pool's LPTokens balance doesn't change
+
4762 BEAST_EXPECT(ammAlice.expectBalances(
+
4763 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
+
4764 // Carol is Liquidity Provider
+
4765 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{5'000'000}));
+
4766 BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
+
4767 // Carol votes
+
4768 ammAlice.vote(carol, 1'000);
+
4769 BEAST_EXPECT(ammAlice.expectTradingFee(500));
+
4770 ammAlice.vote(carol, 0);
+
4771 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
4772 // Carol bids
+
4773 env(ammAlice.bid({.account = carol, .bidMin = 100}));
+
4774 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4'999'900}));
+
4775 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{100}));
+
4776 BEAST_EXPECT(
+
4777 accountBalance(env, carol) ==
+
4778 std::to_string(22500000000 - 4 * baseFee));
+
4779 priceXRP = ammAssetOut(
+
4780 STAmount{XRPAmount{10'000'000'000}},
+
4781 STAmount{token1, 9'999'900},
+
4782 STAmount{token1, 4'999'900},
+
4783 0);
+
4784 // Carol withdraws
+
4785 ammAlice.withdrawAll(carol, XRP(0));
+
4786 BEAST_EXPECT(
+
4787 accountBalance(env, carol) ==
+
4788 std::to_string(29999949999 - 5 * baseFee));
+
4789 BEAST_EXPECT(ammAlice.expectBalances(
+
4790 XRPAmount{10'000'000'000} - priceXRP,
+
4791 USD(10'000),
+
4792 IOUAmount{5'000'000}));
+
4793 BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
+
4794 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
4795 });
+
4796
+
4797 // Offer crossing with two AMM LPTokens.
+
4798 testAMM([&](AMM& ammAlice, Env& env) {
+
4799 ammAlice.deposit(carol, 1'000'000);
+
4800 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
+
4801 AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000));
+
4802 ammAlice1.deposit(carol, 1'000'000);
+
4803 auto const token1 = ammAlice.lptIssue();
+
4804 auto const token2 = ammAlice1.lptIssue();
+
4805 env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100}),
+
4806 txflags(tfPassive));
+
4807 env.close();
+
4808 BEAST_EXPECT(expectOffers(env, alice, 1));
+
4809 env(offer(carol, STAmount{token2, 100}, STAmount{token1, 100}));
+
4810 env.close();
+
4811 BEAST_EXPECT(
+
4812 expectLine(env, alice, STAmount{token1, 10'000'100}) &&
+
4813 expectLine(env, alice, STAmount{token2, 9'999'900}));
+
4814 BEAST_EXPECT(
+
4815 expectLine(env, carol, STAmount{token2, 1'000'100}) &&
+
4816 expectLine(env, carol, STAmount{token1, 999'900}));
+
4817 BEAST_EXPECT(
+
4818 expectOffers(env, alice, 0) && expectOffers(env, carol, 0));
+
4819 });
+
4820
+
4821 // LPs pay LPTokens directly. Must trust set because the trust line
+
4822 // is checked for the limit, which is 0 in the AMM auto-created
+
4823 // trust line.
+
4824 testAMM([&](AMM& ammAlice, Env& env) {
+
4825 auto const token1 = ammAlice.lptIssue();
+
4826 env.trust(STAmount{token1, 2'000'000}, carol);
+
4827 env.close();
+
4828 ammAlice.deposit(carol, 1'000'000);
+
4829 BEAST_EXPECT(
+
4830 ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
+
4831 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
+
4832 // Pool balance doesn't change, only tokens moved from
+
4833 // one line to another.
+
4834 env(pay(alice, carol, STAmount{token1, 100}));
+
4835 env.close();
+
4836 BEAST_EXPECT(
+
4837 // Alice initial token1 10,000,000 - 100
+
4838 ammAlice.expectLPTokens(alice, IOUAmount{9'999'900, 0}) &&
+
4839 // Carol initial token1 1,000,000 + 100
+
4840 ammAlice.expectLPTokens(carol, IOUAmount{1'000'100, 0}));
+
4841
+
4842 env.trust(STAmount{token1, 20'000'000}, alice);
+
4843 env.close();
+
4844 env(pay(carol, alice, STAmount{token1, 100}));
+
4845 env.close();
+
4846 // Back to the original balance
+
4847 BEAST_EXPECT(
+
4848 ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
+
4849 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
+
4850 });
+
4851 }
+
4852
+
4853 void
+
4854 testAmendment()
+
4855 {
+
4856 testcase("Amendment");
+
4857 using namespace jtx;
+
4858 FeatureBitset const all{supported_amendments()};
+
4859 FeatureBitset const noAMM{all - featureAMM};
+
4860 FeatureBitset const noNumber{all - fixUniversalNumber};
+
4861 FeatureBitset const noAMMAndNumber{
+
4862 all - featureAMM - fixUniversalNumber};
+
4863
+
4864 for (auto const& feature : {noAMM, noNumber, noAMMAndNumber})
+
4865 {
+
4866 Env env{*this, feature};
+
4867 fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
+
4868 AMM amm(env, alice, XRP(1'000), USD(1'000), ter(temDISABLED));
+
4869
+
4870 env(amm.bid({.bidMax = 1000}), ter(temMALFORMED));
+
4871 env(amm.bid({}), ter(temDISABLED));
+
4872 amm.vote(VoteArg{.tfee = 100, .err = ter(temDISABLED)});
+
4873 amm.withdraw(WithdrawArg{.tokens = 100, .err = ter(temMALFORMED)});
+
4874 amm.withdraw(WithdrawArg{.err = ter(temDISABLED)});
+
4875 amm.deposit(
+
4876 DepositArg{.asset1In = USD(100), .err = ter(temDISABLED)});
+
4877 amm.ammDelete(alice, ter(temDISABLED));
+
4878 }
+
4879 }
+
4880
+
4881 void
+
4882 testFlags()
+
4883 {
+
4884 testcase("Flags");
+
4885 using namespace jtx;
+
4886
+
4887 testAMM([&](AMM& ammAlice, Env& env) {
+
4888 auto const info = env.rpc(
+
4889 "json",
+
4890 "account_info",
+
4891 std::string(
+
4892 "{\"account\": \"" + to_string(ammAlice.ammAccount()) +
+
4893 "\"}"));
+
4894 auto const flags =
+
4895 info[jss::result][jss::account_data][jss::Flags].asUInt();
+
4896 BEAST_EXPECT(
+
4897 flags ==
+
4898 (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
+
4899 });
+
4900 }
+
4901
+
4902 void
+
4903 testRippling()
+
4904 {
+
4905 testcase("Rippling");
+
4906 using namespace jtx;
+
4907
+
4908 // Rippling via AMM fails because AMM trust line has 0 limit.
+
4909 // Set up two issuers, A and B. Have each issue a token called TST.
+
4910 // Have another account C hold TST from both issuers,
+
4911 // and create an AMM for this pair.
+
4912 // Have a fourth account, D, create a trust line to the AMM for TST.
+
4913 // Send a payment delivering TST.AMM from C to D, using SendMax in
+
4914 // TST.A (or B) and a path through the AMM account. By normal
+
4915 // rippling rules, this would have caused the AMM's balances
+
4916 // to shift at a 1:1 rate with no fee applied has it not been
+
4917 // for 0 limit.
+
4918 {
+
4919 Env env(*this);
+
4920 auto const A = Account("A");
+
4921 auto const B = Account("B");
+
4922 auto const TSTA = A["TST"];
+
4923 auto const TSTB = B["TST"];
+
4924 auto const C = Account("C");
+
4925 auto const D = Account("D");
+
4926
+
4927 env.fund(XRP(10'000), A);
+
4928 env.fund(XRP(10'000), B);
+
4929 env.fund(XRP(10'000), C);
+
4930 env.fund(XRP(10'000), D);
+
4931
+
4932 env.trust(TSTA(10'000), C);
+
4933 env.trust(TSTB(10'000), C);
+
4934 env(pay(A, C, TSTA(10'000)));
+
4935 env(pay(B, C, TSTB(10'000)));
+
4936 AMM amm(env, C, TSTA(5'000), TSTB(5'000));
+
4937 auto const ammIss = Issue(TSTA.currency, amm.ammAccount());
+
4938
+
4939 // Can SetTrust only for AMM LP tokens
+
4940 env(trust(D, STAmount{ammIss, 10'000}), ter(tecNO_PERMISSION));
+
4941 env.close();
+
4942
+
4943 // The payment would fail because of above, but check just in case
+
4944 env(pay(C, D, STAmount{ammIss, 10}),
+
4945 sendmax(TSTA(100)),
+
4946 path(amm.ammAccount()),
+
4947 txflags(tfPartialPayment | tfNoRippleDirect),
+
4948 ter(tecPATH_DRY));
+
4949 }
+
4950 }
+
4951
+
4952 void
+
4953 testAMMAndCLOB(FeatureBitset features)
+
4954 {
+
4955 testcase("AMMAndCLOB, offer quality change");
+
4956 using namespace jtx;
+
4957 auto const gw = Account("gw");
+
4958 auto const TST = gw["TST"];
+
4959 auto const LP1 = Account("LP1");
+
4960 auto const LP2 = Account("LP2");
+
4961
+
4962 auto prep = [&](auto const& offerCb, auto const& expectCb) {
+
4963 Env env(*this, features);
+
4964 env.fund(XRP(30'000'000'000), gw);
+
4965 env(offer(gw, XRP(11'500'000'000), TST(1'000'000'000)));
+
4966
+
4967 env.fund(XRP(10'000), LP1);
+
4968 env.fund(XRP(10'000), LP2);
+
4969 env(offer(LP1, TST(25), XRPAmount(287'500'000)));
+
4970
+
4971 // Either AMM or CLOB offer
+
4972 offerCb(env);
+
4973
+
4974 env(offer(LP2, TST(25), XRPAmount(287'500'000)));
+
4975
+
4976 expectCb(env);
+
4977 };
+
4978
+
4979 // If we replace AMM with an equivalent CLOB offer, which AMM generates
+
4980 // when it is consumed, then the result must be equivalent, too.
+
4981 std::string lp2TSTBalance;
+
4982 std::string lp2TakerGets;
+
4983 std::string lp2TakerPays;
+
4984 // Execute with AMM first
+
4985 prep(
+
4986 [&](Env& env) { AMM amm(env, LP1, TST(25), XRP(250)); },
+
4987 [&](Env& env) {
+
4988 lp2TSTBalance =
+
4989 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
+
4990 .asString();
+
4991 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
+
4992 lp2TakerGets = offer["taker_gets"].asString();
+
4993 lp2TakerPays = offer["taker_pays"]["value"].asString();
+
4994 });
+
4995 // Execute with CLOB offer
+
4996 prep(
+
4997 [&](Env& env) {
+
4998 if (!features[fixAMMv1_1])
+
4999 env(offer(
+
5000 LP1,
+
5001 XRPAmount{18'095'133},
+
5002 STAmount{TST, UINT64_C(1'68737984885388), -14}),
+
5003 txflags(tfPassive));
+
5004 else
+
5005 env(offer(
+
5006 LP1,
+
5007 XRPAmount{18'095'132},
+
5008 STAmount{TST, UINT64_C(1'68737976189735), -14}),
+
5009 txflags(tfPassive));
+
5010 },
+
5011 [&](Env& env) {
+
5012 BEAST_EXPECT(
+
5013 lp2TSTBalance ==
+
5014 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
+
5015 .asString());
+
5016 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
+
5017 BEAST_EXPECT(lp2TakerGets == offer["taker_gets"].asString());
+
5018 BEAST_EXPECT(
+
5019 lp2TakerPays == offer["taker_pays"]["value"].asString());
+
5020 });
+
5021 }
+
5022
+
5023 void
+
5024 testTradingFee(FeatureBitset features)
+
5025 {
+
5026 testcase("Trading Fee");
+
5027 using namespace jtx;
+
5028
+
5029 // Single Deposit, 1% fee
+
5030 testAMM(
+
5031 [&](AMM& ammAlice, Env& env) {
+
5032 // No fee
+
5033 ammAlice.deposit(carol, USD(3'000));
+
5034 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
+
5035 ammAlice.withdrawAll(carol, USD(3'000));
+
5036 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
5037 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
5038 // Set fee to 1%
+
5039 ammAlice.vote(alice, 1'000);
+
5040 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
+
5041 // Carol gets fewer LPToken ~994, because of the single deposit
+
5042 // fee
+
5043 ammAlice.deposit(carol, USD(3'000));
+
5044 BEAST_EXPECT(ammAlice.expectLPTokens(
+
5045 carol, IOUAmount{994'981155689671, -12}));
+
5046 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
+
5047 // Set fee to 0
+
5048 ammAlice.vote(alice, 0);
+
5049 ammAlice.withdrawAll(carol, USD(0));
+
5050 // Carol gets back less than the original deposit
+
5051 BEAST_EXPECT(expectLine(
+
5052 env,
+
5053 carol,
+
5054 STAmount{USD, UINT64_C(29'994'96220068281), -11}));
+
5055 },
+
5056 {{USD(1'000), EUR(1'000)}},
+
5057 0,
+
5058 std::nullopt,
+
5059 {features});
+
5060
+
5061 // Single deposit with EP not exceeding specified:
+
5062 // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut). 1% fee.
+
5063 testAMM(
+
5064 [&](AMM& ammAlice, Env& env) {
+
5065 auto const balance = env.balance(carol, USD);
+
5066 auto tokensFee = ammAlice.deposit(
+
5067 carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
+
5068 auto const deposit = balance - env.balance(carol, USD);
+
5069 ammAlice.withdrawAll(carol, USD(0));
+
5070 ammAlice.vote(alice, 0);
+
5071 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
5072 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
+
5073 // carol pays ~2008 LPTokens in fees or ~0.5% of the no-fee
+
5074 // LPTokens
+
5075 BEAST_EXPECT(tokensFee == IOUAmount(485'636'0611129, -7));
+
5076 BEAST_EXPECT(tokensNoFee == IOUAmount(487'644'85901109, -8));
+
5077 },
+
5078 std::nullopt,
+
5079 1'000,
+
5080 std::nullopt,
+
5081 {features});
+
5082
+
5083 // Single deposit with EP not exceeding specified:
+
5084 // 200USD with EP not to exceed 0.002020 (AssetIn/TokensOut). 1% fee
+
5085 testAMM(
+
5086 [&](AMM& ammAlice, Env& env) {
+
5087 auto const balance = env.balance(carol, USD);
+
5088 auto const tokensFee = ammAlice.deposit(
+
5089 carol, USD(200), std::nullopt, STAmount{USD, 2020, -6});
+
5090 auto const deposit = balance - env.balance(carol, USD);
+
5091 ammAlice.withdrawAll(carol, USD(0));
+
5092 ammAlice.vote(alice, 0);
+
5093 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
5094 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
+
5095 // carol pays ~475 LPTokens in fees or ~0.5% of the no-fee
+
5096 // LPTokens
+
5097 BEAST_EXPECT(tokensFee == IOUAmount(98'000'00000002, -8));
+
5098 BEAST_EXPECT(tokensNoFee == IOUAmount(98'475'81871545, -8));
+
5099 },
+
5100 std::nullopt,
+
5101 1'000,
+
5102 std::nullopt,
+
5103 {features});
+
5104
+
5105 // Single Withdrawal, 1% fee
+
5106 testAMM(
+
5107 [&](AMM& ammAlice, Env& env) {
+
5108 // No fee
+
5109 ammAlice.deposit(carol, USD(3'000));
+
5110
+
5111 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
+
5112 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
+
5113 // Set fee to 1%
+
5114 ammAlice.vote(alice, 1'000);
+
5115 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
+
5116 // Single withdrawal. Carol gets ~5USD less than deposited.
+
5117 ammAlice.withdrawAll(carol, USD(0));
+
5118 BEAST_EXPECT(expectLine(
+
5119 env,
+
5120 carol,
+
5121 STAmount{USD, UINT64_C(29'994'97487437186), -11}));
+
5122 },
+
5123 {{USD(1'000), EUR(1'000)}},
+
5124 0,
+
5125 std::nullopt,
+
5126 {features});
+
5127
+
5128 // Withdraw with EPrice limit, 1% fee.
+
5129 testAMM(
+
5130 [&](AMM& ammAlice, Env& env) {
+
5131 ammAlice.deposit(carol, 1'000'000);
+
5132 auto const tokensFee = ammAlice.withdraw(
+
5133 carol, USD(100), std::nullopt, IOUAmount{520, 0});
+
5134 // carol withdraws ~1,443.44USD
+
5135 auto const balanceAfterWithdraw = [&]() {
+
5136 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
+
5137 return STAmount(USD, UINT64_C(30'443'43891402715), -11);
+
5138 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
+
5139 return STAmount(USD, UINT64_C(30'443'43891402714), -11);
+
5140 else
+
5141 return STAmount(USD, UINT64_C(30'443'43891402713), -11);
+
5142 }();
+
5143 BEAST_EXPECT(env.balance(carol, USD) == balanceAfterWithdraw);
+
5144 // Set to original pool size
+
5145 auto const deposit = balanceAfterWithdraw - USD(29'000);
+
5146 ammAlice.deposit(carol, deposit);
+
5147 // fee 0%
+
5148 ammAlice.vote(alice, 0);
+
5149 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
5150 auto const tokensNoFee = ammAlice.withdraw(carol, deposit);
+
5151 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
+
5152 BEAST_EXPECT(
+
5153 env.balance(carol, USD) ==
+
5154 STAmount(USD, UINT64_C(30'443'43891402717), -11));
+
5155 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
+
5156 BEAST_EXPECT(
+
5157 env.balance(carol, USD) ==
+
5158 STAmount(USD, UINT64_C(30'443'43891402716), -11));
+
5159 else
+
5160 BEAST_EXPECT(
+
5161 env.balance(carol, USD) ==
+
5162 STAmount(USD, UINT64_C(30'443'43891402713), -11));
+
5163 // carol pays ~4008 LPTokens in fees or ~0.5% of the no-fee
+
5164 // LPTokens
+
5165 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
+
5166 BEAST_EXPECT(
+
5167 tokensNoFee == IOUAmount(746'579'80779913, -8));
+
5168 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
+
5169 BEAST_EXPECT(
+
5170 tokensNoFee == IOUAmount(746'579'80779912, -8));
+
5171 else
+
5172 BEAST_EXPECT(
+
5173 tokensNoFee == IOUAmount(746'579'80779911, -8));
+
5174 BEAST_EXPECT(tokensFee == IOUAmount(750'588'23529411, -8));
+
5175 },
+
5176 std::nullopt,
+
5177 1'000,
+
5178 std::nullopt,
+
5179 {features});
+
5180
+
5181 // Payment, 1% fee
+
5182 testAMM(
+
5183 [&](AMM& ammAlice, Env& env) {
+
5184 fund(
+
5185 env,
+
5186 gw,
+
5187 {bob},
+
5188 XRP(1'000),
+
5189 {USD(1'000), EUR(1'000)},
+
5190 Fund::Acct);
+
5191 // Alice contributed 1010EUR and 1000USD to the pool
+
5192 BEAST_EXPECT(expectLine(env, alice, EUR(28'990)));
+
5193 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
+
5194 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
5195 // Carol pays to Alice with no fee
+
5196 env(pay(carol, alice, EUR(10)),
+
5197 path(~EUR),
+
5198 sendmax(USD(10)),
+
5199 txflags(tfNoRippleDirect));
+
5200 env.close();
+
5201 // Alice has 10EUR more and Carol has 10USD less
+
5202 BEAST_EXPECT(expectLine(env, alice, EUR(29'000)));
+
5203 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
+
5204 BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
+
5205
+
5206 // Set fee to 1%
+
5207 ammAlice.vote(alice, 1'000);
+
5208 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
+
5209 // Bob pays to Carol with 1% fee
+
5210 env(pay(bob, carol, USD(10)),
+
5211 path(~USD),
+
5212 sendmax(EUR(15)),
+
5213 txflags(tfNoRippleDirect));
+
5214 env.close();
+
5215 // Bob sends 10.1~EUR to pay 10USD
+
5216 BEAST_EXPECT(expectLine(
+
5217 env, bob, STAmount{EUR, UINT64_C(989'8989898989899), -13}));
+
5218 // Carol got 10USD
+
5219 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
5220 BEAST_EXPECT(ammAlice.expectBalances(
+
5221 USD(1'000),
+
5222 STAmount{EUR, UINT64_C(1'010'10101010101), -11},
+
5223 ammAlice.tokens()));
+
5224 },
+
5225 {{USD(1'000), EUR(1'010)}},
+
5226 0,
+
5227 std::nullopt,
+
5228 {features});
+
5229
+
5230 // Offer crossing, 0.5% fee
+
5231 testAMM(
+
5232 [&](AMM& ammAlice, Env& env) {
+
5233 // No fee
+
5234 env(offer(carol, EUR(10), USD(10)));
+
5235 env.close();
+
5236 BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
+
5237 BEAST_EXPECT(expectLine(env, carol, EUR(30'010)));
+
5238 // Change pool composition back
+
5239 env(offer(carol, USD(10), EUR(10)));
+
5240 env.close();
+
5241 // Set fee to 0.5%
+
5242 ammAlice.vote(alice, 500);
+
5243 BEAST_EXPECT(ammAlice.expectTradingFee(500));
+
5244 env(offer(carol, EUR(10), USD(10)));
+
5245 env.close();
+
5246 // Alice gets fewer ~4.97EUR for ~5.02USD, the difference goes
+
5247 // to the pool
+
5248 BEAST_EXPECT(expectLine(
+
5249 env,
+
5250 carol,
+
5251 STAmount{USD, UINT64_C(29'995'02512562814), -11}));
+
5252 BEAST_EXPECT(expectLine(
+
5253 env,
+
5254 carol,
+
5255 STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
+
5256 BEAST_EXPECT(expectOffers(
+
5257 env,
+
5258 carol,
+
5259 1,
+
5260 {{Amounts{
+
5261 STAmount{EUR, UINT64_C(5'025125628140703), -15},
+
5262 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
+
5263 if (!features[fixAMMv1_1])
+
5264 {
+
5265 BEAST_EXPECT(ammAlice.expectBalances(
+
5266 STAmount{USD, UINT64_C(1'004'974874371859), -12},
+
5267 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
+
5268 ammAlice.tokens()));
+
5269 }
+
5270 else
+
5271 {
+
5272 BEAST_EXPECT(ammAlice.expectBalances(
+
5273 STAmount{USD, UINT64_C(1'004'97487437186), -11},
+
5274 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
+
5275 ammAlice.tokens()));
+
5276 }
+
5277 },
+
5278 {{USD(1'000), EUR(1'010)}},
+
5279 0,
+
5280 std::nullopt,
+
5281 {features});
+
5282
+
5283 // Payment with AMM and CLOB offer, 0 fee
+
5284 // AMM liquidity is consumed first up to CLOB offer quality
+
5285 // CLOB offer is fully consumed next
+
5286 // Remaining amount is consumed via AMM liquidity
+
5287 {
+
5288 Env env(*this, features);
+
5289 Account const ed("ed");
+
5290 fund(
+
5291 env,
+
5292 gw,
+
5293 {alice, bob, carol, ed},
+
5294 XRP(1'000),
+
5295 {USD(2'000), EUR(2'000)});
+
5296 env(offer(carol, EUR(5), USD(5)));
+
5297 AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
+
5298 env(pay(bob, ed, USD(10)),
+
5299 path(~USD),
+
5300 sendmax(EUR(15)),
+
5301 txflags(tfNoRippleDirect));
+
5302 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
+
5303 if (!features[fixAMMv1_1])
+
5304 {
+
5305 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
+
5306 BEAST_EXPECT(ammAlice.expectBalances(
+
5307 USD(1'000), EUR(1'005), ammAlice.tokens()));
+
5308 }
+
5309 else
+
5310 {
+
5311 BEAST_EXPECT(expectLine(
+
5312 env, bob, STAmount(EUR, UINT64_C(1989'999999999999), -12)));
+
5313 BEAST_EXPECT(ammAlice.expectBalances(
+
5314 USD(1'000),
+
5315 STAmount(EUR, UINT64_C(1005'000000000001), -12),
+
5316 ammAlice.tokens()));
+
5317 }
+
5318 BEAST_EXPECT(expectOffers(env, carol, 0));
+
5319 }
+
5320
+
5321 // Payment with AMM and CLOB offer. Same as above but with 0.25%
+
5322 // fee.
+
5323 {
+
5324 Env env(*this, features);
+
5325 Account const ed("ed");
+
5326 fund(
+
5327 env,
+
5328 gw,
+
5329 {alice, bob, carol, ed},
+
5330 XRP(1'000),
+
5331 {USD(2'000), EUR(2'000)});
+
5332 env(offer(carol, EUR(5), USD(5)));
+
5333 // Set 0.25% fee
+
5334 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 250);
+
5335 env(pay(bob, ed, USD(10)),
+
5336 path(~USD),
+
5337 sendmax(EUR(15)),
+
5338 txflags(tfNoRippleDirect));
+
5339 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
+
5340 if (!features[fixAMMv1_1])
+
5341 {
+
5342 BEAST_EXPECT(expectLine(
+
5343 env,
+
5344 bob,
+
5345 STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
+
5346 BEAST_EXPECT(ammAlice.expectBalances(
+
5347 USD(1'000),
+
5348 STAmount{EUR, UINT64_C(1'005'012546992382), -12},
+
5349 ammAlice.tokens()));
+
5350 }
+
5351 else
+
5352 {
+
5353 BEAST_EXPECT(expectLine(
+
5354 env,
+
5355 bob,
+
5356 STAmount{EUR, UINT64_C(1'989'987453007628), -12}));
+
5357 BEAST_EXPECT(ammAlice.expectBalances(
+
5358 USD(1'000),
+
5359 STAmount{EUR, UINT64_C(1'005'012546992372), -12},
+
5360 ammAlice.tokens()));
+
5361 }
+
5362 BEAST_EXPECT(expectOffers(env, carol, 0));
+
5363 }
+
5364
+
5365 // Payment with AMM and CLOB offer. AMM has a better
+
5366 // spot price quality, but 1% fee offsets that. As the result
+
5367 // the entire trade is executed via LOB.
+
5368 {
+
5369 Env env(*this, features);
+
5370 Account const ed("ed");
+
5371 fund(
+
5372 env,
+
5373 gw,
+
5374 {alice, bob, carol, ed},
+
5375 XRP(1'000),
+
5376 {USD(2'000), EUR(2'000)});
+
5377 env(offer(carol, EUR(10), USD(10)));
+
5378 // Set 1% fee
+
5379 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
+
5380 env(pay(bob, ed, USD(10)),
+
5381 path(~USD),
+
5382 sendmax(EUR(15)),
+
5383 txflags(tfNoRippleDirect));
+
5384 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
+
5385 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
+
5386 BEAST_EXPECT(ammAlice.expectBalances(
+
5387 USD(1'005), EUR(1'000), ammAlice.tokens()));
+
5388 BEAST_EXPECT(expectOffers(env, carol, 0));
+
5389 }
+
5390
+
5391 // Payment with AMM and CLOB offer. AMM has a better
+
5392 // spot price quality, but 1% fee offsets that.
+
5393 // The CLOB offer is consumed first and the remaining
+
5394 // amount is consumed via AMM liquidity.
+
5395 {
+
5396 Env env(*this, features);
+
5397 Account const ed("ed");
+
5398 fund(
+
5399 env,
+
5400 gw,
+
5401 {alice, bob, carol, ed},
+
5402 XRP(1'000),
+
5403 {USD(2'000), EUR(2'000)});
+
5404 env(offer(carol, EUR(9), USD(9)));
+
5405 // Set 1% fee
+
5406 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
+
5407 env(pay(bob, ed, USD(10)),
+
5408 path(~USD),
+
5409 sendmax(EUR(15)),
+
5410 txflags(tfNoRippleDirect));
+
5411 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
+
5412 BEAST_EXPECT(expectLine(
+
5413 env, bob, STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
+
5414 BEAST_EXPECT(ammAlice.expectBalances(
+
5415 USD(1'004),
+
5416 STAmount{EUR, UINT64_C(1'001'006076703288), -12},
+
5417 ammAlice.tokens()));
+
5418 BEAST_EXPECT(expectOffers(env, carol, 0));
+
5419 }
+
5420 }
+
5421
+
5422 void
+
5423 testAdjustedTokens(FeatureBitset features)
+
5424 {
+
5425 testcase("Adjusted Deposit/Withdraw Tokens");
+
5426
+
5427 using namespace jtx;
+
5428
+
5429 // Deposit/Withdraw in USD
+
5430 testAMM(
+
5431 [&](AMM& ammAlice, Env& env) {
+
5432 Account const bob("bob");
+
5433 Account const ed("ed");
+
5434 Account const paul("paul");
+
5435 Account const dan("dan");
+
5436 Account const chris("chris");
+
5437 Account const simon("simon");
+
5438 Account const ben("ben");
+
5439 Account const nataly("nataly");
+
5440 fund(
+
5441 env,
+
5442 gw,
+
5443 {bob, ed, paul, dan, chris, simon, ben, nataly},
+
5444 {USD(1'500'000)},
+
5445 Fund::Acct);
+
5446 for (int i = 0; i < 10; ++i)
+
5447 {
+
5448 ammAlice.deposit(ben, STAmount{USD, 1, -10});
+
5449 ammAlice.withdrawAll(ben, USD(0));
+
5450 ammAlice.deposit(simon, USD(0.1));
+
5451 ammAlice.withdrawAll(simon, USD(0));
+
5452 ammAlice.deposit(chris, USD(1));
+
5453 ammAlice.withdrawAll(chris, USD(0));
+
5454 ammAlice.deposit(dan, USD(10));
+
5455 ammAlice.withdrawAll(dan, USD(0));
+
5456 ammAlice.deposit(bob, USD(100));
+
5457 ammAlice.withdrawAll(bob, USD(0));
+
5458 ammAlice.deposit(carol, USD(1'000));
+
5459 ammAlice.withdrawAll(carol, USD(0));
+
5460 ammAlice.deposit(ed, USD(10'000));
+
5461 ammAlice.withdrawAll(ed, USD(0));
+
5462 ammAlice.deposit(paul, USD(100'000));
+
5463 ammAlice.withdrawAll(paul, USD(0));
+
5464 ammAlice.deposit(nataly, USD(1'000'000));
+
5465 ammAlice.withdrawAll(nataly, USD(0));
+
5466 }
+
5467 // Due to round off some accounts have a tiny gain, while
+
5468 // other have a tiny loss. The last account to withdraw
+
5469 // gets everything in the pool.
+
5470 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
+
5471 BEAST_EXPECT(ammAlice.expectBalances(
+
5472 XRP(10'000),
+
5473 STAmount{USD, UINT64_C(10'000'0000000013), -10},
+
5474 IOUAmount{10'000'000}));
+
5475 else if (features[fixAMMv1_3])
+
5476 BEAST_EXPECT(ammAlice.expectBalances(
+
5477 XRP(10'000),
+
5478 STAmount{USD, UINT64_C(10'000'0000000003), -10},
+
5479 IOUAmount{10'000'000}));
+
5480 else
+
5481 BEAST_EXPECT(ammAlice.expectBalances(
+
5482 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
+
5483 BEAST_EXPECT(expectLine(env, ben, USD(1'500'000)));
+
5484 BEAST_EXPECT(expectLine(env, simon, USD(1'500'000)));
+
5485 BEAST_EXPECT(expectLine(env, chris, USD(1'500'000)));
+
5486 BEAST_EXPECT(expectLine(env, dan, USD(1'500'000)));
+
5487 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
+
5488 BEAST_EXPECT(expectLine(
+
5489 env,
+
5490 carol,
+
5491 STAmount{USD, UINT64_C(30'000'00000000001), -11}));
+
5492 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
+
5493 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
5494 else
+
5495 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
5496 BEAST_EXPECT(expectLine(env, ed, USD(1'500'000)));
+
5497 BEAST_EXPECT(expectLine(env, paul, USD(1'500'000)));
+
5498 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
+
5499 BEAST_EXPECT(expectLine(
+
5500 env,
+
5501 nataly,
+
5502 STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
+
5503 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
+
5504 BEAST_EXPECT(expectLine(
+
5505 env,
+
5506 nataly,
+
5507 STAmount{USD, UINT64_C(1'500'000'000000005), -9}));
+
5508 else
+
5509 BEAST_EXPECT(expectLine(env, nataly, USD(1'500'000)));
+
5510 ammAlice.withdrawAll(alice);
+
5511 BEAST_EXPECT(!ammAlice.ammExists());
+
5512 if (!features[fixAMMv1_1])
+
5513 BEAST_EXPECT(expectLine(
+
5514 env,
+
5515 alice,
+
5516 STAmount{USD, UINT64_C(30'000'0000000013), -10}));
+
5517 else if (features[fixAMMv1_3])
+
5518 BEAST_EXPECT(expectLine(
+
5519 env,
+
5520 alice,
+
5521 STAmount{USD, UINT64_C(30'000'0000000003), -10}));
+
5522 else
+
5523 BEAST_EXPECT(expectLine(env, alice, USD(30'000)));
+
5524 // alice XRP balance is 30,000initial - 50 ammcreate fee -
+
5525 // 10drops fee
+
5526 BEAST_EXPECT(
+
5527 accountBalance(env, alice) ==
+
5528 std::to_string(
+
5529 29950000000 - env.current()->fees().base.drops()));
+
5530 },
+
5531 std::nullopt,
+
5532 0,
+
5533 std::nullopt,
+
5534 {features});
+
5535
+
5536 // Same as above but deposit/withdraw in XRP
+
5537 testAMM(
+
5538 [&](AMM& ammAlice, Env& env) {
+
5539 Account const bob("bob");
+
5540 Account const ed("ed");
+
5541 Account const paul("paul");
+
5542 Account const dan("dan");
+
5543 Account const chris("chris");
+
5544 Account const simon("simon");
+
5545 Account const ben("ben");
+
5546 Account const nataly("nataly");
+
5547 fund(
+
5548 env,
+
5549 gw,
+
5550 {bob, ed, paul, dan, chris, simon, ben, nataly},
+
5551 XRP(2'000'000),
+
5552 {},
+
5553 Fund::Acct);
+
5554 for (int i = 0; i < 10; ++i)
+
5555 {
+
5556 ammAlice.deposit(ben, XRPAmount{1});
+
5557 ammAlice.withdrawAll(ben, XRP(0));
+
5558 ammAlice.deposit(simon, XRPAmount(1'000));
+
5559 ammAlice.withdrawAll(simon, XRP(0));
+
5560 ammAlice.deposit(chris, XRP(1));
+
5561 ammAlice.withdrawAll(chris, XRP(0));
+
5562 ammAlice.deposit(dan, XRP(10));
+
5563 ammAlice.withdrawAll(dan, XRP(0));
+
5564 ammAlice.deposit(bob, XRP(100));
+
5565 ammAlice.withdrawAll(bob, XRP(0));
+
5566 ammAlice.deposit(carol, XRP(1'000));
+
5567 ammAlice.withdrawAll(carol, XRP(0));
+
5568 ammAlice.deposit(ed, XRP(10'000));
+
5569 ammAlice.withdrawAll(ed, XRP(0));
+
5570 ammAlice.deposit(paul, XRP(100'000));
+
5571 ammAlice.withdrawAll(paul, XRP(0));
+
5572 ammAlice.deposit(nataly, XRP(1'000'000));
+
5573 ammAlice.withdrawAll(nataly, XRP(0));
+
5574 }
+
5575 auto const baseFee = env.current()->fees().base.drops();
+
5576 if (!features[fixAMMv1_3])
+
5577 {
+
5578 // No round off with XRP in this test
+
5579 BEAST_EXPECT(ammAlice.expectBalances(
+
5580 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
+
5581 ammAlice.withdrawAll(alice);
+
5582 BEAST_EXPECT(!ammAlice.ammExists());
+
5583 // 20,000 initial - (deposit+withdraw) * 10
+
5584 auto const xrpBalance =
+
5585 (XRP(2'000'000) - txfee(env, 20)).getText();
+
5586 BEAST_EXPECT(accountBalance(env, ben) == xrpBalance);
+
5587 BEAST_EXPECT(accountBalance(env, simon) == xrpBalance);
+
5588 BEAST_EXPECT(accountBalance(env, chris) == xrpBalance);
+
5589 BEAST_EXPECT(accountBalance(env, dan) == xrpBalance);
+
5590
+
5591 // 30,000 initial - (deposit+withdraw) * 10
+
5592 BEAST_EXPECT(
+
5593 accountBalance(env, carol) ==
+
5594 std::to_string(30'000'000'000 - 20 * baseFee));
+
5595 BEAST_EXPECT(accountBalance(env, ed) == xrpBalance);
+
5596 BEAST_EXPECT(accountBalance(env, paul) == xrpBalance);
+
5597 BEAST_EXPECT(accountBalance(env, nataly) == xrpBalance);
+
5598 // 30,000 initial - 50 ammcreate fee - 10drops withdraw fee
+
5599 BEAST_EXPECT(
+
5600 accountBalance(env, alice) ==
+
5601 std::to_string(29'950'000'000 - baseFee));
+
5602 }
+
5603 else
+
5604 {
+
5605 // post-amendment the rounding takes place to ensure
+
5606 // AMM invariant
+
5607 BEAST_EXPECT(ammAlice.expectBalances(
+
5608 XRPAmount(10'000'000'080),
+
5609 USD(10'000),
+
5610 IOUAmount{10'000'000}));
+
5611 ammAlice.withdrawAll(alice);
+
5612 BEAST_EXPECT(!ammAlice.ammExists());
+
5613 auto const xrpBalance =
+
5614 XRP(2'000'000) - txfee(env, 20) - drops(10);
+
5615 auto const xrpBalanceText = xrpBalance.getText();
+
5616 BEAST_EXPECT(accountBalance(env, ben) == xrpBalanceText);
+
5617 BEAST_EXPECT(accountBalance(env, simon) == xrpBalanceText);
+
5618 BEAST_EXPECT(accountBalance(env, chris) == xrpBalanceText);
+
5619 BEAST_EXPECT(accountBalance(env, dan) == xrpBalanceText);
+
5620 BEAST_EXPECT(
+
5621 accountBalance(env, carol) ==
+
5622 std::to_string(30'000'000'000 - 20 * baseFee - 10));
+
5623 BEAST_EXPECT(
+
5624 accountBalance(env, ed) ==
+
5625 (xrpBalance + drops(2)).getText());
+
5626 BEAST_EXPECT(
+
5627 accountBalance(env, paul) ==
+
5628 (xrpBalance + drops(3)).getText());
+
5629 BEAST_EXPECT(
+
5630 accountBalance(env, nataly) ==
+
5631 (xrpBalance + drops(5)).getText());
+
5632 BEAST_EXPECT(
+
5633 accountBalance(env, alice) ==
+
5634 std::to_string(29'950'000'000 - baseFee + 80));
+
5635 }
+
5636 },
+
5637 std::nullopt,
+
5638 0,
+
5639 std::nullopt,
+
5640 {features});
+
5641 }
+
5642
+
5643 void
+
5644 testAutoDelete()
+
5645 {
+
5646 testcase("Auto Delete");
+
5647
+
5648 using namespace jtx;
+
5649 FeatureBitset const all{supported_amendments()};
+
5650
+
5651 {
+
5652 Env env(
+
5653 *this,
+
5654 envconfig([](std::unique_ptr<Config> cfg) {
+
5655 cfg->FEES.reference_fee = XRPAmount(1);
+
5656 return cfg;
+
5657 }),
+
5658 all);
+
5659 fund(env, gw, {alice}, XRP(20'000), {USD(10'000)});
+
5660 AMM amm(env, gw, XRP(10'000), USD(10'000));
+
5661 for (auto i = 0; i < maxDeletableAMMTrustLines + 10; ++i)
+
5662 {
+
5663 Account const a{std::to_string(i)};
+
5664 env.fund(XRP(1'000), a);
+
5665 env(trust(a, STAmount{amm.lptIssue(), 10'000}));
+
5666 env.close();
+
5667 }
+
5668 // The trustlines are partially deleted,
+
5669 // AMM is set to an empty state.
+
5670 amm.withdrawAll(gw);
+
5671 BEAST_EXPECT(amm.ammExists());
+
5672
+
5673 // Bid,Vote,Deposit,Withdraw,SetTrust failing with
+
5674 // tecAMM_EMPTY. Deposit succeeds with tfTwoAssetIfEmpty option.
+
5675 env(amm.bid({
+
5676 .account = alice,
+
5677 .bidMin = 1000,
+
5678 }),
+
5679 ter(tecAMM_EMPTY));
+
5680 amm.vote(
+
5681 std::nullopt,
+
5682 100,
+
5683 std::nullopt,
+
5684 std::nullopt,
+
5685 std::nullopt,
+
5686 ter(tecAMM_EMPTY));
+
5687 amm.withdraw(
+
5688 alice, 100, std::nullopt, std::nullopt, ter(tecAMM_EMPTY));
+
5689 amm.deposit(
+
5690 alice,
+
5691 USD(100),
+
5692 std::nullopt,
+
5693 std::nullopt,
+
5694 std::nullopt,
+
5695 ter(tecAMM_EMPTY));
+
5696 env(trust(alice, STAmount{amm.lptIssue(), 10'000}),
+
5697 ter(tecAMM_EMPTY));
+
5698
+
5699 // Can deposit with tfTwoAssetIfEmpty option
+
5700 amm.deposit(
+
5701 alice,
+
5702 std::nullopt,
+
5703 XRP(10'000),
+
5704 USD(10'000),
+
5705 std::nullopt,
+
5706 tfTwoAssetIfEmpty,
+
5707 std::nullopt,
+
5708 std::nullopt,
+
5709 1'000);
+
5710 BEAST_EXPECT(
+
5711 amm.expectBalances(XRP(10'000), USD(10'000), amm.tokens()));
+
5712 BEAST_EXPECT(amm.expectTradingFee(1'000));
+
5713 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
+
5714
+
5715 // Withdrawing all tokens deletes AMM since the number
+
5716 // of remaining trustlines is less than max
+
5717 amm.withdrawAll(alice);
+
5718 BEAST_EXPECT(!amm.ammExists());
+
5719 BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount())));
+
5720 }
+
5721
+
5722 {
+
5723 Env env(
+
5724 *this,
+
5725 envconfig([](std::unique_ptr<Config> cfg) {
+
5726 cfg->FEES.reference_fee = XRPAmount(1);
+
5727 return cfg;
+
5728 }),
+
5729 all);
+
5730 fund(env, gw, {alice}, XRP(20'000), {USD(10'000)});
+
5731 AMM amm(env, gw, XRP(10'000), USD(10'000));
+
5732 for (auto i = 0; i < maxDeletableAMMTrustLines * 2 + 10; ++i)
+
5733 {
+
5734 Account const a{std::to_string(i)};
+
5735 env.fund(XRP(1'000), a);
+
5736 env(trust(a, STAmount{amm.lptIssue(), 10'000}));
+
5737 env.close();
+
5738 }
+
5739 // The trustlines are partially deleted.
+
5740 amm.withdrawAll(gw);
+
5741 BEAST_EXPECT(amm.ammExists());
+
5742
+
5743 // AMMDelete has to be called twice to delete AMM.
+
5744 amm.ammDelete(alice, ter(tecINCOMPLETE));
+
5745 BEAST_EXPECT(amm.ammExists());
+
5746 // Deletes remaining trustlines and deletes AMM.
+
5747 amm.ammDelete(alice);
+
5748 BEAST_EXPECT(!amm.ammExists());
+
5749 BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount())));
+
5750
+
5751 // Try redundant delete
+
5752 amm.ammDelete(alice, ter(terNO_AMM));
+
5753 }
+
5754 }
+
5755
+
5756 void
+
5757 testClawback()
+
5758 {
+
5759 testcase("Clawback");
+
5760 using namespace jtx;
+
5761 Env env(*this);
+
5762 env.fund(XRP(2'000), gw);
+
5763 env.fund(XRP(2'000), alice);
+
5764 AMM amm(env, gw, XRP(1'000), USD(1'000));
+
5765 env(fset(gw, asfAllowTrustLineClawback), ter(tecOWNERS));
+
5766 }
+
5767
+
5768 void
+
5769 testAMMID()
+
5770 {
+
5771 testcase("AMMID");
+
5772 using namespace jtx;
+
5773 testAMM([&](AMM& amm, Env& env) {
+
5774 amm.setClose(false);
+
5775 auto const info = env.rpc(
+
5776 "json",
+
5777 "account_info",
+
5778 std::string(
+
5779 "{\"account\": \"" + to_string(amm.ammAccount()) + "\"}"));
+
5780 try
+
5781 {
+
5782 BEAST_EXPECT(
+
5783 info[jss::result][jss::account_data][jss::AMMID]
+
5784 .asString() == to_string(amm.ammID()));
+
5785 }
+
5786 catch (...)
+
5787 {
+
5788 fail();
+
5789 }
+
5790 amm.deposit(carol, 1'000);
+
5791 auto affected = env.meta()->getJson(
+
5792 JsonOptions::none)[sfAffectedNodes.fieldName];
+
5793 try
+
5794 {
+
5795 bool found = false;
+
5796 for (auto const& node : affected)
+
5797 {
+
5798 if (node.isMember(sfModifiedNode.fieldName) &&
+
5799 node[sfModifiedNode.fieldName]
+
5800 [sfLedgerEntryType.fieldName]
+
5801 .asString() == "AccountRoot" &&
+
5802 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
+
5803 [jss::Account]
+
5804 .asString() == to_string(amm.ammAccount()))
+
5805 {
+
5806 found = node[sfModifiedNode.fieldName]
+
5807 [sfFinalFields.fieldName][jss::AMMID]
+
5808 .asString() == to_string(amm.ammID());
+
5809 break;
+
5810 }
+
5811 }
+
5812 BEAST_EXPECT(found);
+
5813 }
+
5814 catch (...)
+
5815 {
+
5816 fail();
+
5817 }
+
5818 });
+
5819 }
+
5820
+
5821 void
+
5822 testSelection(FeatureBitset features)
+
5823 {
+
5824 testcase("Offer/Strand Selection");
+
5825 using namespace jtx;
+
5826 Account const ed("ed");
+
5827 Account const gw1("gw1");
+
5828 auto const ETH = gw1["ETH"];
+
5829 auto const CAN = gw1["CAN"];
+
5830
+
5831 // These tests are expected to fail if the OwnerPaysFee feature
+
5832 // is ever supported. Updates will need to be made to AMM handling
+
5833 // in the payment engine, and these tests will need to be updated.
+
5834
+
5835 auto prep = [&](Env& env, auto gwRate, auto gw1Rate) {
+
5836 fund(env, gw, {alice, carol, bob, ed}, XRP(2'000), {USD(2'000)});
+
5837 env.fund(XRP(2'000), gw1);
+
5838 fund(
+
5839 env,
+
5840 gw1,
+
5841 {alice, carol, bob, ed},
+
5842 {ETH(2'000), CAN(2'000)},
+
5843 Fund::IOUOnly);
+
5844 env(rate(gw, gwRate));
+
5845 env(rate(gw1, gw1Rate));
+
5846 env.close();
+
5847 };
+
5848
+
5849 for (auto const& rates :
+
5850 {std::make_pair(1.5, 1.9), std::make_pair(1.9, 1.5)})
+
5851 {
+
5852 // Offer Selection
+
5853
+
5854 // Cross-currency payment: AMM has the same spot price quality
+
5855 // as CLOB's offer and can't generate a better quality offer.
+
5856 // The transfer fee in this case doesn't change the CLOB quality
+
5857 // because trIn is ignored on adjustment and trOut on payment is
+
5858 // also ignored because ownerPaysTransferFee is false in this
+
5859 // case. Run test for 0) offer, 1) AMM, 2) offer and AMM to
+
5860 // verify that the quality is better in the first case, and CLOB
+
5861 // is selected in the second case.
+
5862 {
+
5863 std::array<Quality, 3> q;
+
5864 for (auto i = 0; i < 3; ++i)
+
5865 {
+
5866 Env env(*this, features);
+
5867 prep(env, rates.first, rates.second);
+
5868 std::optional<AMM> amm;
+
5869 if (i == 0 || i == 2)
+
5870 {
+
5871 env(offer(ed, ETH(400), USD(400)), txflags(tfPassive));
+
5872 env.close();
+
5873 }
+
5874 if (i > 0)
+
5875 amm.emplace(env, ed, USD(1'000), ETH(1'000));
+
5876 env(pay(carol, bob, USD(100)),
+
5877 path(~USD),
+
5878 sendmax(ETH(500)));
+
5879 env.close();
+
5880 // CLOB and AMM, AMM is not selected
+
5881 if (i == 2)
5882 {
-
5883 env(offer(ed, ETH(400), CAN(400)), txflags(tfPassive));
-
5884 env(offer(ed, CAN(400), USD(400))), txflags(tfPassive);
-
5885 env.close();
-
5886 }
-
5887
-
5888 if (i > 0)
-
5889 amm.emplace(env, ed, ETH(1'000), USD(1'000));
-
5890
-
5891 env(pay(carol, bob, USD(100)),
-
5892 path(~USD),
-
5893 path(~CAN, ~USD),
-
5894 sendmax(ETH(600)));
-
5895 env.close();
-
5896
-
5897 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
-
5898
-
5899 if (i == 2 && !features[fixAMMv1_1])
-
5900 {
-
5901 if (rates.first == 1.5)
-
5902 {
-
5903 // Liquidity is consumed from AMM strand only
-
5904 BEAST_EXPECT(amm->expectBalances(
-
5905 STAmount{ETH, UINT64_C(1'176'66038955758), -11},
-
5906 USD(850),
-
5907 amm->tokens()));
-
5908 }
-
5909 else
-
5910 {
-
5911 BEAST_EXPECT(amm->expectBalances(
-
5912 STAmount{
-
5913 ETH, UINT64_C(1'179'540094339627), -12},
-
5914 STAmount{USD, UINT64_C(847'7880529867501), -13},
-
5915 amm->tokens()));
-
5916 BEAST_EXPECT(expectOffers(
-
5917 env,
-
5918 ed,
-
5919 2,
-
5920 {{Amounts{
-
5921 STAmount{
-
5922 ETH,
-
5923 UINT64_C(343'3179205198749),
-
5924 -13},
-
5925 STAmount{
-
5926 CAN,
-
5927 UINT64_C(343'3179205198749),
-
5928 -13},
-
5929 },
-
5930 Amounts{
-
5931 STAmount{
-
5932 CAN,
-
5933 UINT64_C(362'2119470132499),
-
5934 -13},
-
5935 STAmount{
-
5936 USD,
-
5937 UINT64_C(362'2119470132499),
-
5938 -13},
-
5939 }}}));
-
5940 }
-
5941 }
-
5942 else if (i == 2)
-
5943 {
-
5944 if (rates.first == 1.5)
-
5945 {
-
5946 // Liquidity is consumed from AMM strand only
-
5947 BEAST_EXPECT(amm->expectBalances(
-
5948 STAmount{
-
5949 ETH, UINT64_C(1'176'660389557593), -12},
-
5950 USD(850),
-
5951 amm->tokens()));
-
5952 }
-
5953 else
-
5954 {
-
5955 BEAST_EXPECT(amm->expectBalances(
-
5956 STAmount{ETH, UINT64_C(1'179'54009433964), -11},
-
5957 STAmount{USD, UINT64_C(847'7880529867501), -13},
-
5958 amm->tokens()));
-
5959 BEAST_EXPECT(expectOffers(
-
5960 env,
-
5961 ed,
-
5962 2,
-
5963 {{Amounts{
-
5964 STAmount{
-
5965 ETH,
-
5966 UINT64_C(343'3179205198749),
-
5967 -13},
-
5968 STAmount{
-
5969 CAN,
-
5970 UINT64_C(343'3179205198749),
-
5971 -13},
-
5972 },
-
5973 Amounts{
-
5974 STAmount{
-
5975 CAN,
-
5976 UINT64_C(362'2119470132499),
-
5977 -13},
-
5978 STAmount{
-
5979 USD,
-
5980 UINT64_C(362'2119470132499),
-
5981 -13},
-
5982 }}}));
-
5983 }
-
5984 }
-
5985 q[i] = Quality(Amounts{
-
5986 ETH(2'000) - env.balance(carol, ETH),
-
5987 env.balance(bob, USD) - USD(2'000)});
-
5988 }
-
5989 BEAST_EXPECT(q[1] > q[0]);
-
5990 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
-
5991 }
-
5992 }
-
5993 }
-
5994
-
5995 void
-
5996 testFixDefaultInnerObj()
-
5997 {
-
5998 testcase("Fix Default Inner Object");
-
5999 using namespace jtx;
-
6000 FeatureBitset const all{supported_amendments()};
-
6001
-
6002 auto test = [&](FeatureBitset features,
-
6003 TER const& err1,
-
6004 TER const& err2,
-
6005 TER const& err3,
-
6006 TER const& err4,
-
6007 std::uint16_t tfee,
-
6008 bool closeLedger,
-
6009 std::optional<std::uint16_t> extra = std::nullopt) {
-
6010 Env env(*this, features);
-
6011 fund(env, gw, {alice}, XRP(1'000), {USD(10)});
-
6012 AMM amm(
-
6013 env,
-
6014 gw,
-
6015 XRP(10),
-
6016 USD(10),
-
6017 {.tfee = tfee, .close = closeLedger});
-
6018 amm.deposit(alice, USD(10), XRP(10));
-
6019 amm.vote(VoteArg{.account = alice, .tfee = tfee, .err = ter(err1)});
-
6020 amm.withdraw(WithdrawArg{
-
6021 .account = gw, .asset1Out = USD(1), .err = ter(err2)});
-
6022 // with the amendment disabled and ledger not closed,
-
6023 // second vote succeeds if the first vote sets the trading fee
-
6024 // to non-zero; if the first vote sets the trading fee to >0 &&
-
6025 // <9 then the second withdraw succeeds if the second vote sets
-
6026 // the trading fee so that the discounted fee is non-zero
-
6027 amm.vote(VoteArg{.account = alice, .tfee = 20, .err = ter(err3)});
-
6028 amm.withdraw(WithdrawArg{
-
6029 .account = gw, .asset1Out = USD(2), .err = ter(err4)});
-
6030 };
-
6031
-
6032 // ledger is closed after each transaction, vote/withdraw don't fail
-
6033 // regardless whether the amendment is enabled or not
-
6034 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, true);
-
6035 test(
-
6036 all - fixInnerObjTemplate,
-
6037 tesSUCCESS,
-
6038 tesSUCCESS,
-
6039 tesSUCCESS,
-
6040 tesSUCCESS,
-
6041 0,
-
6042 true);
-
6043 // ledger is not closed after each transaction
-
6044 // vote/withdraw don't fail if the amendment is enabled
-
6045 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, false);
-
6046 // vote/withdraw fail if the amendment is not enabled
-
6047 // second vote/withdraw still fail: second vote fails because
-
6048 // the initial trading fee is 0, consequently second withdraw fails
-
6049 // because the second vote fails
-
6050 test(
-
6051 all - fixInnerObjTemplate,
-
6052 tefEXCEPTION,
-
6053 tefEXCEPTION,
-
6054 tefEXCEPTION,
-
6055 tefEXCEPTION,
-
6056 0,
-
6057 false);
-
6058 // if non-zero trading/discounted fee then vote/withdraw
-
6059 // don't fail whether the ledger is closed or not and
-
6060 // the amendment is enabled or not
-
6061 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, true);
-
6062 test(
-
6063 all - fixInnerObjTemplate,
-
6064 tesSUCCESS,
-
6065 tesSUCCESS,
-
6066 tesSUCCESS,
-
6067 tesSUCCESS,
-
6068 10,
-
6069 true);
-
6070 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, false);
-
6071 test(
-
6072 all - fixInnerObjTemplate,
-
6073 tesSUCCESS,
-
6074 tesSUCCESS,
-
6075 tesSUCCESS,
-
6076 tesSUCCESS,
-
6077 10,
-
6078 false);
-
6079 // non-zero trading fee but discounted fee is 0, vote doesn't fail
-
6080 // but withdraw fails
-
6081 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 9, false);
-
6082 // second vote sets the trading fee to non-zero, consequently
-
6083 // second withdraw doesn't fail even if the amendment is not
-
6084 // enabled and the ledger is not closed
-
6085 test(
-
6086 all - fixInnerObjTemplate,
-
6087 tesSUCCESS,
-
6088 tefEXCEPTION,
-
6089 tesSUCCESS,
-
6090 tesSUCCESS,
-
6091 9,
-
6092 false);
-
6093 }
-
6094
-
6095 void
-
6096 testFixChangeSpotPriceQuality(FeatureBitset features)
-
6097 {
-
6098 testcase("Fix changeSpotPriceQuality");
-
6099 using namespace jtx;
-
6100
-
6101 std::string logs;
-
6102
-
6103 enum class Status {
-
6104 SucceedShouldSucceedResize, // Succeed in pre-fix because
-
6105 // error allowance, succeed post-fix
-
6106 // because of offer resizing
-
6107 FailShouldSucceed, // Fail in pre-fix due to rounding,
-
6108 // succeed after fix because of XRP
-
6109 // side is generated first
-
6110 SucceedShouldFail, // Succeed in pre-fix, fail after fix
-
6111 // due to small quality difference
-
6112 Fail, // Both fail because the quality can't be matched
-
6113 Succeed // Both succeed
-
6114 };
-
6115 using enum Status;
-
6116 auto const xrpIouAmounts10_100 =
-
6117 TAmounts{XRPAmount{10}, IOUAmount{100}};
-
6118 auto const iouXrpAmounts10_100 =
-
6119 TAmounts{IOUAmount{10}, XRPAmount{100}};
-
6120 // clang-format off
-
6121 std::vector<std::tuple<std::string, std::string, Quality, std::uint16_t, Status>> tests = {
-
6122 //Pool In , Pool Out, Quality , Fee, Status
-
6123 {"0.001519763260828713", "1558701", Quality{5414253689393440221}, 1000, FailShouldSucceed},
-
6124 {"0.01099814367603737", "1892611", Quality{5482264816516900274}, 1000, FailShouldSucceed},
-
6125 {"0.78", "796599", Quality{5630392334958379008}, 1000, FailShouldSucceed},
-
6126 {"105439.2955578965", "49398693", Quality{5910869983721805038}, 400, FailShouldSucceed},
-
6127 {"12408293.23445213", "4340810521", Quality{5911611095910090752}, 997, FailShouldSucceed},
-
6128 {"1892611", "0.01099814367603737", Quality{6703103457950430139}, 1000, FailShouldSucceed},
-
6129 {"423028.8508101858", "3392804520", Quality{5837920340654162816}, 600, FailShouldSucceed},
-
6130 {"44565388.41001027", "73890647", Quality{6058976634606450001}, 1000, FailShouldSucceed},
-
6131 {"66831.68494832662", "16", Quality{6346111134641742975}, 0, FailShouldSucceed},
-
6132 {"675.9287302203422", "1242632304", Quality{5625960929244093294}, 300, FailShouldSucceed},
-
6133 {"7047.112186735699", "1649845866", Quality{5696855348026306945}, 504, FailShouldSucceed},
-
6134 {"840236.4402981238", "47419053", Quality{5982561601648018688}, 499, FailShouldSucceed},
-
6135 {"992715.618909774", "189445631733", Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
-
6136 {"504636667521", "185545883.9506651", Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
-
6137 {"992706.7218636649", "189447316000", Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
-
6138 {"1.068737911388205", "127860278877", Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
-
6139 {"17932506.56880419", "189308.6043676173", Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
-
6140 {"1.066379294658174", "128042251493", Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
-
6141 {"350131413924", "1576879.110907892", Quality{6487411636539049449}, 650, Fail},
-
6142 {"422093460", "2.731797662057464", Quality{6702911108534394924}, 1000, Fail},
-
6143 {"76128132223", "367172.7148422662", Quality{6487263463413514240}, 548, Fail},
-
6144 {"132701839250", "280703770.7695443", Quality{6273750681188885075}, 562, Fail},
-
6145 {"994165.7604612011", "189551302411", Quality{5697835592690668727}, 815, Fail},
-
6146 {"45053.33303227917", "86612695359", Quality{5625695218943638190}, 500, Fail},
-
6147 {"199649.077043865", "14017933007", Quality{5766034667318524880}, 324, Fail},
-
6148 {"27751824831.70903", "78896950", Quality{6272538159621630432}, 500, Fail},
-
6149 {"225.3731275781907", "156431793648", Quality{5477818047604078924}, 989, Fail},
-
6150 {"199649.077043865", "14017933007", Quality{5766036094462806309}, 324, Fail},
-
6151 {"3.590272027140361", "20677643641", Quality{5406056147042156356}, 808, Fail},
-
6152 {"1.070884664490231", "127604712776", Quality{5268620608623825741}, 293, Fail},
-
6153 {"3272.448829820197", "6275124076", Quality{5625710328924117902}, 81, Fail},
-
6154 {"0.009059512633902926", "7994028", Quality{5477511954775533172}, 1000, Fail},
-
6155 {"1", "1.0", Quality{0}, 100, Fail},
-
6156 {"1.0", "1", Quality{0}, 100, Fail},
-
6157 {"10", "10.0", Quality{xrpIouAmounts10_100}, 100, Fail},
-
6158 {"10.0", "10", Quality{iouXrpAmounts10_100}, 100, Fail},
-
6159 {"69864389131", "287631.4543025075", Quality{6487623473313516078}, 451, Succeed},
-
6160 {"4328342973", "12453825.99247381", Quality{6272522264364865181}, 997, Succeed},
-
6161 {"32347017", "7003.93031579449", Quality{6347261126087916670}, 1000, Succeed},
-
6162 {"61697206161", "36631.4583206413", Quality{6558965195382476659}, 500, Succeed},
-
6163 {"1654524979", "7028.659825511603", Quality{6487551345110052981}, 504, Succeed},
-
6164 {"88621.22277293179", "5128418948", Quality{5766347291552869205}, 380, Succeed},
-
6165 {"1892611", "0.01099814367603737", Quality{6703102780512015436}, 1000, Succeed},
-
6166 {"4542.639373338766", "24554809", Quality{5838994982188783710}, 0, Succeed},
-
6167 {"5132932546", "88542.99750172683", Quality{6419203342950054537}, 380, Succeed},
-
6168 {"78929964.1549083", "1506494795", Quality{5986890029845558688}, 589, Succeed},
-
6169 {"10096561906", "44727.72453735605", Quality{6487455290284644551}, 250, Succeed},
-
6170 {"5092.219565514988", "8768257694", Quality{5626349534958379008}, 503, Succeed},
-
6171 {"1819778294", "8305.084302902864", Quality{6487429398998540860}, 415, Succeed},
-
6172 {"6970462.633911943", "57359281", Quality{6054087899185946624}, 850, Succeed},
-
6173 {"3983448845", "2347.543644281467", Quality{6558965195382476659}, 856, Succeed},
-
6174 // This is a tiny offer 12drops/19321952e-15 it succeeds pre-amendment because of the error allowance.
-
6175 // Post amendment it is resized to 11drops/17711789e-15 but the quality is still less than
-
6176 // the target quality and the offer fails.
-
6177 {"771493171", "1.243473020567508", Quality{6707566798038544272}, 100, SucceedShouldFail},
-
6178 };
-
6179 // clang-format on
-
6180
-
6181 boost::regex rx("^\\d+$");
-
6182 boost::smatch match;
-
6183 // tests that succeed should have the same amounts pre-fix and post-fix
-
6184 std::vector<std::pair<STAmount, STAmount>> successAmounts;
-
6185 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
-
6186 auto rules = env.current()->rules();
-
6187 CurrentTransactionRulesGuard rg(rules);
-
6188 for (auto const& t : tests)
-
6189 {
-
6190 auto getPool = [&](std::string const& v, bool isXRP) {
-
6191 if (isXRP)
-
6192 return amountFromString(xrpIssue(), v);
-
6193 return amountFromString(noIssue(), v);
-
6194 };
-
6195 auto const& quality = std::get<Quality>(t);
-
6196 auto const tfee = std::get<std::uint16_t>(t);
-
6197 auto const status = std::get<Status>(t);
-
6198 auto const poolInIsXRP =
-
6199 boost::regex_search(std::get<0>(t), match, rx);
-
6200 auto const poolOutIsXRP =
-
6201 boost::regex_search(std::get<1>(t), match, rx);
-
6202 assert(!(poolInIsXRP && poolOutIsXRP));
-
6203 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
-
6204 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
-
6205 try
-
6206 {
-
6207 auto const amounts = changeSpotPriceQuality(
-
6208 Amounts{poolIn, poolOut},
-
6209 quality,
-
6210 tfee,
-
6211 env.current()->rules(),
-
6212 env.journal);
-
6213 if (amounts)
-
6214 {
-
6215 if (status == SucceedShouldSucceedResize)
-
6216 {
-
6217 if (!features[fixAMMv1_1])
-
6218 BEAST_EXPECT(Quality{*amounts} < quality);
-
6219 else
-
6220 BEAST_EXPECT(Quality{*amounts} >= quality);
-
6221 }
-
6222 else if (status == Succeed)
-
6223 {
-
6224 if (!features[fixAMMv1_1])
-
6225 BEAST_EXPECT(
-
6226 Quality{*amounts} >= quality ||
-
6227 withinRelativeDistance(
-
6228 Quality{*amounts}, quality, Number{1, -7}));
-
6229 else
-
6230 BEAST_EXPECT(Quality{*amounts} >= quality);
-
6231 }
-
6232 else if (status == FailShouldSucceed)
-
6233 {
-
6234 BEAST_EXPECT(
-
6235 features[fixAMMv1_1] &&
-
6236 Quality{*amounts} >= quality);
-
6237 }
-
6238 else if (status == SucceedShouldFail)
-
6239 {
-
6240 BEAST_EXPECT(
-
6241 !features[fixAMMv1_1] &&
-
6242 Quality{*amounts} < quality &&
-
6243 withinRelativeDistance(
-
6244 Quality{*amounts}, quality, Number{1, -7}));
-
6245 }
-
6246 }
-
6247 else
-
6248 {
-
6249 // Fails pre- and post-amendment because the quality can't
-
6250 // be matched. Verify by generating a tiny offer, which
-
6251 // doesn't match the quality. Exclude zero quality since
-
6252 // no offer is generated in this case.
-
6253 if (status == Fail && quality != Quality{0})
-
6254 {
-
6255 auto tinyOffer = [&]() {
-
6256 if (isXRP(poolIn))
-
6257 {
-
6258 auto const takerPays = STAmount{xrpIssue(), 1};
-
6259 return Amounts{
-
6260 takerPays,
-
6261 swapAssetIn(
-
6262 Amounts{poolIn, poolOut},
-
6263 takerPays,
-
6264 tfee)};
-
6265 }
-
6266 else if (isXRP(poolOut))
-
6267 {
-
6268 auto const takerGets = STAmount{xrpIssue(), 1};
-
6269 return Amounts{
-
6270 swapAssetOut(
-
6271 Amounts{poolIn, poolOut},
-
6272 takerGets,
-
6273 tfee),
-
6274 takerGets};
-
6275 }
-
6276 auto const takerPays = toAmount<STAmount>(
-
6277 getIssue(poolIn), Number{1, -10} * poolIn);
-
6278 return Amounts{
-
6279 takerPays,
-
6280 swapAssetIn(
-
6281 Amounts{poolIn, poolOut}, takerPays, tfee)};
-
6282 }();
-
6283 BEAST_EXPECT(Quality(tinyOffer) < quality);
-
6284 }
-
6285 else if (status == FailShouldSucceed)
-
6286 {
-
6287 BEAST_EXPECT(!features[fixAMMv1_1]);
-
6288 }
-
6289 else if (status == SucceedShouldFail)
-
6290 {
-
6291 BEAST_EXPECT(features[fixAMMv1_1]);
-
6292 }
-
6293 }
-
6294 }
-
6295 catch (std::runtime_error const& e)
-
6296 {
-
6297 BEAST_EXPECT(
-
6298 !strcmp(e.what(), "changeSpotPriceQuality failed"));
-
6299 BEAST_EXPECT(
-
6300 !features[fixAMMv1_1] && status == FailShouldSucceed);
-
6301 }
-
6302 }
+
5883 BEAST_EXPECT(amm->expectBalances(
+
5884 USD(1'000), ETH(1'000), amm->tokens()));
+
5885 }
+
5886 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
+
5887 q[i] = Quality(Amounts{
+
5888 ETH(2'000) - env.balance(carol, ETH),
+
5889 env.balance(bob, USD) - USD(2'000)});
+
5890 }
+
5891 // CLOB is better quality than AMM
+
5892 BEAST_EXPECT(q[0] > q[1]);
+
5893 // AMM is not selected with CLOB
+
5894 BEAST_EXPECT(q[0] == q[2]);
+
5895 }
+
5896 // Offer crossing: AMM has the same spot price quality
+
5897 // as CLOB's offer and can't generate a better quality offer.
+
5898 // The transfer fee in this case doesn't change the CLOB quality
+
5899 // because the quality adjustment is ignored for the offer
+
5900 // crossing.
+
5901 for (auto i = 0; i < 3; ++i)
+
5902 {
+
5903 Env env(*this, features);
+
5904 prep(env, rates.first, rates.second);
+
5905 std::optional<AMM> amm;
+
5906 if (i == 0 || i == 2)
+
5907 {
+
5908 env(offer(ed, ETH(400), USD(400)), txflags(tfPassive));
+
5909 env.close();
+
5910 }
+
5911 if (i > 0)
+
5912 amm.emplace(env, ed, USD(1'000), ETH(1'000));
+
5913 env(offer(alice, USD(400), ETH(400)));
+
5914 env.close();
+
5915 // AMM is not selected
+
5916 if (i > 0)
+
5917 {
+
5918 BEAST_EXPECT(amm->expectBalances(
+
5919 USD(1'000), ETH(1'000), amm->tokens()));
+
5920 }
+
5921 if (i == 0 || i == 2)
+
5922 {
+
5923 // Fully crosses
+
5924 BEAST_EXPECT(expectOffers(env, alice, 0));
+
5925 }
+
5926 // Fails to cross because AMM is not selected
+
5927 else
+
5928 {
+
5929 BEAST_EXPECT(expectOffers(
+
5930 env, alice, 1, {Amounts{USD(400), ETH(400)}}));
+
5931 }
+
5932 BEAST_EXPECT(expectOffers(env, ed, 0));
+
5933 }
+
5934
+
5935 // Show that the CLOB quality reduction
+
5936 // results in AMM offer selection.
+
5937
+
5938 // Same as the payment but reduced offer quality
+
5939 {
+
5940 std::array<Quality, 3> q;
+
5941 for (auto i = 0; i < 3; ++i)
+
5942 {
+
5943 Env env(*this, features);
+
5944 prep(env, rates.first, rates.second);
+
5945 std::optional<AMM> amm;
+
5946 if (i == 0 || i == 2)
+
5947 {
+
5948 env(offer(ed, ETH(400), USD(300)), txflags(tfPassive));
+
5949 env.close();
+
5950 }
+
5951 if (i > 0)
+
5952 amm.emplace(env, ed, USD(1'000), ETH(1'000));
+
5953 env(pay(carol, bob, USD(100)),
+
5954 path(~USD),
+
5955 sendmax(ETH(500)));
+
5956 env.close();
+
5957 // AMM and CLOB are selected
+
5958 if (i > 0)
+
5959 {
+
5960 BEAST_EXPECT(!amm->expectBalances(
+
5961 USD(1'000), ETH(1'000), amm->tokens()));
+
5962 }
+
5963 if (i == 2 && !features[fixAMMv1_1])
+
5964 {
+
5965 if (rates.first == 1.5)
+
5966 {
+
5967 if (!features[fixAMMv1_1])
+
5968 BEAST_EXPECT(expectOffers(
+
5969 env,
+
5970 ed,
+
5971 1,
+
5972 {{Amounts{
+
5973 STAmount{
+
5974 ETH,
+
5975 UINT64_C(378'6327949540823),
+
5976 -13},
+
5977 STAmount{
+
5978 USD,
+
5979 UINT64_C(283'9745962155617),
+
5980 -13}}}}));
+
5981 else
+
5982 BEAST_EXPECT(expectOffers(
+
5983 env,
+
5984 ed,
+
5985 1,
+
5986 {{Amounts{
+
5987 STAmount{
+
5988 ETH,
+
5989 UINT64_C(378'6327949540813),
+
5990 -13},
+
5991 STAmount{
+
5992 USD,
+
5993 UINT64_C(283'974596215561),
+
5994 -12}}}}));
+
5995 }
+
5996 else
+
5997 {
+
5998 if (!features[fixAMMv1_1])
+
5999 BEAST_EXPECT(expectOffers(
+
6000 env,
+
6001 ed,
+
6002 1,
+
6003 {{Amounts{
+
6004 STAmount{
+
6005 ETH,
+
6006 UINT64_C(325'299461620749),
+
6007 -12},
+
6008 STAmount{
+
6009 USD,
+
6010 UINT64_C(243'9745962155617),
+
6011 -13}}}}));
+
6012 else
+
6013 BEAST_EXPECT(expectOffers(
+
6014 env,
+
6015 ed,
+
6016 1,
+
6017 {{Amounts{
+
6018 STAmount{
+
6019 ETH,
+
6020 UINT64_C(325'299461620748),
+
6021 -12},
+
6022 STAmount{
+
6023 USD,
+
6024 UINT64_C(243'974596215561),
+
6025 -12}}}}));
+
6026 }
+
6027 }
+
6028 else if (i == 2)
+
6029 {
+
6030 if (rates.first == 1.5)
+
6031 {
+
6032 BEAST_EXPECT(expectOffers(
+
6033 env,
+
6034 ed,
+
6035 1,
+
6036 {{Amounts{
+
6037 STAmount{
+
6038 ETH, UINT64_C(378'6327949540812), -13},
+
6039 STAmount{
+
6040 USD,
+
6041 UINT64_C(283'9745962155609),
+
6042 -13}}}}));
+
6043 }
+
6044 else
+
6045 {
+
6046 BEAST_EXPECT(expectOffers(
+
6047 env,
+
6048 ed,
+
6049 1,
+
6050 {{Amounts{
+
6051 STAmount{
+
6052 ETH, UINT64_C(325'2994616207479), -13},
+
6053 STAmount{
+
6054 USD,
+
6055 UINT64_C(243'9745962155609),
+
6056 -13}}}}));
+
6057 }
+
6058 }
+
6059 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
+
6060 q[i] = Quality(Amounts{
+
6061 ETH(2'000) - env.balance(carol, ETH),
+
6062 env.balance(bob, USD) - USD(2'000)});
+
6063 }
+
6064 // AMM is better quality
+
6065 BEAST_EXPECT(q[1] > q[0]);
+
6066 // AMM and CLOB produce better quality
+
6067 BEAST_EXPECT(q[2] > q[1]);
+
6068 }
+
6069
+
6070 // Same as the offer-crossing but reduced offer quality
+
6071 for (auto i = 0; i < 3; ++i)
+
6072 {
+
6073 Env env(*this, features);
+
6074 prep(env, rates.first, rates.second);
+
6075 std::optional<AMM> amm;
+
6076 if (i == 0 || i == 2)
+
6077 {
+
6078 env(offer(ed, ETH(400), USD(250)), txflags(tfPassive));
+
6079 env.close();
+
6080 }
+
6081 if (i > 0)
+
6082 amm.emplace(env, ed, USD(1'000), ETH(1'000));
+
6083 env(offer(alice, USD(250), ETH(400)));
+
6084 env.close();
+
6085 // AMM is selected in both cases
+
6086 if (i > 0)
+
6087 {
+
6088 BEAST_EXPECT(!amm->expectBalances(
+
6089 USD(1'000), ETH(1'000), amm->tokens()));
+
6090 }
+
6091 // Partially crosses, AMM is selected, CLOB fails
+
6092 // limitQuality
+
6093 if (i == 2)
+
6094 {
+
6095 if (rates.first == 1.5)
+
6096 {
+
6097 if (!features[fixAMMv1_1])
+
6098 {
+
6099 BEAST_EXPECT(expectOffers(
+
6100 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
+
6101 BEAST_EXPECT(expectOffers(
+
6102 env,
+
6103 alice,
+
6104 1,
+
6105 {{Amounts{
+
6106 STAmount{
+
6107 USD, UINT64_C(40'5694150420947), -13},
+
6108 STAmount{
+
6109 ETH, UINT64_C(64'91106406735152), -14},
+
6110 }}}));
+
6111 }
+
6112 else
+
6113 {
+
6114 // Ed offer is partially crossed.
+
6115 // The updated rounding makes limitQuality
+
6116 // work if both amendments are enabled
+
6117 BEAST_EXPECT(expectOffers(
+
6118 env,
+
6119 ed,
+
6120 1,
+
6121 {{Amounts{
+
6122 STAmount{
+
6123 ETH, UINT64_C(335'0889359326475), -13},
+
6124 STAmount{
+
6125 USD, UINT64_C(209'4305849579047), -13},
+
6126 }}}));
+
6127 BEAST_EXPECT(expectOffers(env, alice, 0));
+
6128 }
+
6129 }
+
6130 else
+
6131 {
+
6132 if (!features[fixAMMv1_1])
+
6133 {
+
6134 // Ed offer is partially crossed.
+
6135 BEAST_EXPECT(expectOffers(
+
6136 env,
+
6137 ed,
+
6138 1,
+
6139 {{Amounts{
+
6140 STAmount{
+
6141 ETH, UINT64_C(335'0889359326485), -13},
+
6142 STAmount{
+
6143 USD, UINT64_C(209'4305849579053), -13},
+
6144 }}}));
+
6145 BEAST_EXPECT(expectOffers(env, alice, 0));
+
6146 }
+
6147 else
+
6148 {
+
6149 // Ed offer is partially crossed.
+
6150 BEAST_EXPECT(expectOffers(
+
6151 env,
+
6152 ed,
+
6153 1,
+
6154 {{Amounts{
+
6155 STAmount{
+
6156 ETH, UINT64_C(335'0889359326475), -13},
+
6157 STAmount{
+
6158 USD, UINT64_C(209'4305849579047), -13},
+
6159 }}}));
+
6160 BEAST_EXPECT(expectOffers(env, alice, 0));
+
6161 }
+
6162 }
+
6163 }
+
6164 }
+
6165
+
6166 // Strand selection
+
6167
+
6168 // Two book steps strand quality is 1.
+
6169 // AMM strand's best quality is equal to AMM's spot price
+
6170 // quality, which is 1. Both strands (steps) are adjusted
+
6171 // for the transfer fee in qualityUpperBound. In case
+
6172 // of two strands, AMM offers have better quality and are
+
6173 // consumed first, remaining liquidity is generated by CLOB
+
6174 // offers. Liquidity from two strands is better in this case
+
6175 // than in case of one strand with two book steps. Liquidity
+
6176 // from one strand with AMM has better quality than either one
+
6177 // strand with two book steps or two strands. It may appear
+
6178 // unintuitive, but one strand with AMM is optimized and
+
6179 // generates one AMM offer, while in case of two strands,
+
6180 // multiple AMM offers are generated, which results in slightly
+
6181 // worse overall quality.
+
6182 {
+
6183 std::array<Quality, 3> q;
+
6184 for (auto i = 0; i < 3; ++i)
+
6185 {
+
6186 Env env(*this, features);
+
6187 prep(env, rates.first, rates.second);
+
6188 std::optional<AMM> amm;
+
6189
+
6190 if (i == 0 || i == 2)
+
6191 {
+
6192 env(offer(ed, ETH(400), CAN(400)), txflags(tfPassive));
+
6193 env(offer(ed, CAN(400), USD(400))), txflags(tfPassive);
+
6194 env.close();
+
6195 }
+
6196
+
6197 if (i > 0)
+
6198 amm.emplace(env, ed, ETH(1'000), USD(1'000));
+
6199
+
6200 env(pay(carol, bob, USD(100)),
+
6201 path(~USD),
+
6202 path(~CAN, ~USD),
+
6203 sendmax(ETH(600)));
+
6204 env.close();
+
6205
+
6206 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
+
6207
+
6208 if (i == 2 && !features[fixAMMv1_1])
+
6209 {
+
6210 if (rates.first == 1.5)
+
6211 {
+
6212 // Liquidity is consumed from AMM strand only
+
6213 BEAST_EXPECT(amm->expectBalances(
+
6214 STAmount{ETH, UINT64_C(1'176'66038955758), -11},
+
6215 USD(850),
+
6216 amm->tokens()));
+
6217 }
+
6218 else
+
6219 {
+
6220 BEAST_EXPECT(amm->expectBalances(
+
6221 STAmount{
+
6222 ETH, UINT64_C(1'179'540094339627), -12},
+
6223 STAmount{USD, UINT64_C(847'7880529867501), -13},
+
6224 amm->tokens()));
+
6225 BEAST_EXPECT(expectOffers(
+
6226 env,
+
6227 ed,
+
6228 2,
+
6229 {{Amounts{
+
6230 STAmount{
+
6231 ETH,
+
6232 UINT64_C(343'3179205198749),
+
6233 -13},
+
6234 STAmount{
+
6235 CAN,
+
6236 UINT64_C(343'3179205198749),
+
6237 -13},
+
6238 },
+
6239 Amounts{
+
6240 STAmount{
+
6241 CAN,
+
6242 UINT64_C(362'2119470132499),
+
6243 -13},
+
6244 STAmount{
+
6245 USD,
+
6246 UINT64_C(362'2119470132499),
+
6247 -13},
+
6248 }}}));
+
6249 }
+
6250 }
+
6251 else if (i == 2)
+
6252 {
+
6253 if (rates.first == 1.5)
+
6254 {
+
6255 // Liquidity is consumed from AMM strand only
+
6256 BEAST_EXPECT(amm->expectBalances(
+
6257 STAmount{
+
6258 ETH, UINT64_C(1'176'660389557593), -12},
+
6259 USD(850),
+
6260 amm->tokens()));
+
6261 }
+
6262 else
+
6263 {
+
6264 BEAST_EXPECT(amm->expectBalances(
+
6265 STAmount{ETH, UINT64_C(1'179'54009433964), -11},
+
6266 STAmount{USD, UINT64_C(847'7880529867501), -13},
+
6267 amm->tokens()));
+
6268 BEAST_EXPECT(expectOffers(
+
6269 env,
+
6270 ed,
+
6271 2,
+
6272 {{Amounts{
+
6273 STAmount{
+
6274 ETH,
+
6275 UINT64_C(343'3179205198749),
+
6276 -13},
+
6277 STAmount{
+
6278 CAN,
+
6279 UINT64_C(343'3179205198749),
+
6280 -13},
+
6281 },
+
6282 Amounts{
+
6283 STAmount{
+
6284 CAN,
+
6285 UINT64_C(362'2119470132499),
+
6286 -13},
+
6287 STAmount{
+
6288 USD,
+
6289 UINT64_C(362'2119470132499),
+
6290 -13},
+
6291 }}}));
+
6292 }
+
6293 }
+
6294 q[i] = Quality(Amounts{
+
6295 ETH(2'000) - env.balance(carol, ETH),
+
6296 env.balance(bob, USD) - USD(2'000)});
+
6297 }
+
6298 BEAST_EXPECT(q[1] > q[0]);
+
6299 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
+
6300 }
+
6301 }
+
6302 }
6303
-
6304 // Test negative discriminant
-
6305 {
-
6306 // b**2 - 4 * a * c -> 1 * 1 - 4 * 1 * 1 = -3
-
6307 auto const res =
-
6308 solveQuadraticEqSmallest(Number{1}, Number{1}, Number{1});
-
6309 BEAST_EXPECT(!res.has_value());
-
6310 }
-
6311 }
-
6312
-
6313 void
-
6314 testMalformed()
-
6315 {
-
6316 using namespace jtx;
-
6317
-
6318 testAMM([&](AMM& ammAlice, Env& env) {
-
6319 WithdrawArg args{
-
6320 .flags = tfSingleAsset,
-
6321 .err = ter(temMALFORMED),
-
6322 };
-
6323 ammAlice.withdraw(args);
-
6324 });
-
6325
-
6326 testAMM([&](AMM& ammAlice, Env& env) {
-
6327 WithdrawArg args{
-
6328 .flags = tfOneAssetLPToken,
-
6329 .err = ter(temMALFORMED),
-
6330 };
-
6331 ammAlice.withdraw(args);
-
6332 });
-
6333
-
6334 testAMM([&](AMM& ammAlice, Env& env) {
-
6335 WithdrawArg args{
-
6336 .flags = tfLimitLPToken,
-
6337 .err = ter(temMALFORMED),
-
6338 };
-
6339 ammAlice.withdraw(args);
-
6340 });
-
6341
-
6342 testAMM([&](AMM& ammAlice, Env& env) {
-
6343 WithdrawArg args{
-
6344 .asset1Out = XRP(100),
-
6345 .asset2Out = XRP(100),
-
6346 .err = ter(temBAD_AMM_TOKENS),
-
6347 };
-
6348 ammAlice.withdraw(args);
-
6349 });
-
6350
-
6351 testAMM([&](AMM& ammAlice, Env& env) {
-
6352 WithdrawArg args{
-
6353 .asset1Out = XRP(100),
-
6354 .asset2Out = BAD(100),
-
6355 .err = ter(temBAD_CURRENCY),
-
6356 };
-
6357 ammAlice.withdraw(args);
-
6358 });
-
6359
-
6360 testAMM([&](AMM& ammAlice, Env& env) {
-
6361 Json::Value jv;
-
6362 jv[jss::TransactionType] = jss::AMMWithdraw;
-
6363 jv[jss::Flags] = tfLimitLPToken;
-
6364 jv[jss::Account] = alice.human();
-
6365 ammAlice.setTokens(jv);
-
6366 XRP(100).value().setJson(jv[jss::Amount]);
-
6367 USD(100).value().setJson(jv[jss::EPrice]);
-
6368 env(jv, ter(temBAD_AMM_TOKENS));
-
6369 });
-
6370 }
-
6371
-
6372 void
-
6373 testFixOverflowOffer(FeatureBitset features)
-
6374 {
-
6375 using namespace jtx;
-
6376 using namespace std::chrono;
-
6377 FeatureBitset const all{features};
-
6378
-
6379 std::string logs;
-
6380
-
6381 Account const gatehub{"gatehub"};
-
6382 Account const bitstamp{"bitstamp"};
-
6383 Account const trader{"trader"};
-
6384 auto const usdGH = gatehub["USD"];
-
6385 auto const btcGH = gatehub["BTC"];
-
6386 auto const usdBIT = bitstamp["USD"];
-
6387
-
6388 struct InputSet
-
6389 {
-
6390 char const* testCase;
-
6391 double const poolUsdBIT;
-
6392 double const poolUsdGH;
-
6393 sendmax const sendMaxUsdBIT;
-
6394 STAmount const sendUsdGH;
-
6395 STAmount const failUsdGH;
-
6396 STAmount const failUsdGHr;
-
6397 STAmount const failUsdBIT;
-
6398 STAmount const failUsdBITr;
-
6399 STAmount const goodUsdGH;
-
6400 STAmount const goodUsdGHr;
-
6401 STAmount const goodUsdBIT;
-
6402 STAmount const goodUsdBITr;
-
6403 IOUAmount const lpTokenBalance;
-
6404 double const offer1BtcGH = 0.1;
-
6405 double const offer2BtcGH = 0.1;
-
6406 double const offer2UsdGH = 1;
-
6407 double const rateBIT = 0.0;
-
6408 double const rateGH = 0.0;
-
6409 };
-
6410
-
6411 using uint64_t = std::uint64_t;
-
6412
-
6413 for (auto const& input : {
-
6414 InputSet{
-
6415 .testCase = "Test Fix Overflow Offer", //
-
6416 .poolUsdBIT = 3, //
-
6417 .poolUsdGH = 273, //
-
6418 .sendMaxUsdBIT{usdBIT(50)}, //
-
6419 .sendUsdGH{usdGH, uint64_t(272'455089820359), -12}, //
-
6420 .failUsdGH = STAmount{0}, //
-
6421 .failUsdGHr = STAmount{0}, //
-
6422 .failUsdBIT{usdBIT, uint64_t(46'47826086956522), -14}, //
-
6423 .failUsdBITr{usdBIT, uint64_t(46'47826086956521), -14}, //
-
6424 .goodUsdGH{usdGH, uint64_t(96'7543114220382), -13}, //
-
6425 .goodUsdGHr{usdGH, uint64_t(96'7543114222965), -13}, //
-
6426 .goodUsdBIT{usdBIT, uint64_t(8'464739069120721), -15}, //
-
6427 .goodUsdBITr{usdBIT, uint64_t(8'464739069098152), -15}, //
-
6428 .lpTokenBalance = {28'61817604250837, -14}, //
-
6429 .offer1BtcGH = 0.1, //
-
6430 .offer2BtcGH = 0.1, //
-
6431 .offer2UsdGH = 1, //
-
6432 .rateBIT = 1.15, //
-
6433 .rateGH = 1.2, //
-
6434 },
-
6435 InputSet{
-
6436 .testCase = "Overflow test {1, 100, 0.111}", //
-
6437 .poolUsdBIT = 1, //
-
6438 .poolUsdGH = 100, //
-
6439 .sendMaxUsdBIT{usdBIT(0.111)}, //
-
6440 .sendUsdGH{usdGH, 100}, //
-
6441 .failUsdGH = STAmount{0}, //
-
6442 .failUsdGHr = STAmount{0}, //
-
6443 .failUsdBIT{usdBIT, uint64_t(1'111), -3}, //
-
6444 .failUsdBITr{usdBIT, uint64_t(1'111), -3}, //
-
6445 .goodUsdGH{usdGH, uint64_t(90'04347888284115), -14}, //
-
6446 .goodUsdGHr{usdGH, uint64_t(90'04347888284201), -14}, //
-
6447 .goodUsdBIT{usdBIT, uint64_t(1'111), -3}, //
-
6448 .goodUsdBITr{usdBIT, uint64_t(1'111), -3}, //
-
6449 .lpTokenBalance{10, 0}, //
-
6450 .offer1BtcGH = 1e-5, //
-
6451 .offer2BtcGH = 1, //
-
6452 .offer2UsdGH = 1e-5, //
-
6453 .rateBIT = 0, //
-
6454 .rateGH = 0, //
-
6455 },
-
6456 InputSet{
-
6457 .testCase = "Overflow test {1, 100, 1.00}", //
-
6458 .poolUsdBIT = 1, //
-
6459 .poolUsdGH = 100, //
-
6460 .sendMaxUsdBIT{usdBIT(1.00)}, //
-
6461 .sendUsdGH{usdGH, 100}, //
-
6462 .failUsdGH = STAmount{0}, //
-
6463 .failUsdGHr = STAmount{0}, //
-
6464 .failUsdBIT{usdBIT, uint64_t(2), 0}, //
-
6465 .failUsdBITr{usdBIT, uint64_t(2), 0}, //
-
6466 .goodUsdGH{usdGH, uint64_t(52'94379354424079), -14}, //
-
6467 .goodUsdGHr{usdGH, uint64_t(52'94379354424135), -14}, //
-
6468 .goodUsdBIT{usdBIT, uint64_t(2), 0}, //
-
6469 .goodUsdBITr{usdBIT, uint64_t(2), 0}, //
-
6470 .lpTokenBalance{10, 0}, //
-
6471 .offer1BtcGH = 1e-5, //
-
6472 .offer2BtcGH = 1, //
-
6473 .offer2UsdGH = 1e-5, //
-
6474 .rateBIT = 0, //
-
6475 .rateGH = 0, //
-
6476 },
-
6477 InputSet{
-
6478 .testCase = "Overflow test {1, 100, 4.6432}", //
-
6479 .poolUsdBIT = 1, //
-
6480 .poolUsdGH = 100, //
-
6481 .sendMaxUsdBIT{usdBIT(4.6432)}, //
-
6482 .sendUsdGH{usdGH, 100}, //
-
6483 .failUsdGH = STAmount{0}, //
-
6484 .failUsdGHr = STAmount{0}, //
-
6485 .failUsdBIT{usdBIT, uint64_t(5'6432), -4}, //
-
6486 .failUsdBITr{usdBIT, uint64_t(5'6432), -4}, //
-
6487 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
-
6488 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
-
6489 .goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
-
6490 .goodUsdBITr{usdBIT, uint64_t(2'821579689703954), -15}, //
-
6491 .lpTokenBalance{10, 0}, //
-
6492 .offer1BtcGH = 1e-5, //
-
6493 .offer2BtcGH = 1, //
-
6494 .offer2UsdGH = 1e-5, //
-
6495 .rateBIT = 0, //
-
6496 .rateGH = 0, //
-
6497 },
-
6498 InputSet{
-
6499 .testCase = "Overflow test {1, 100, 10}", //
-
6500 .poolUsdBIT = 1, //
-
6501 .poolUsdGH = 100, //
-
6502 .sendMaxUsdBIT{usdBIT(10)}, //
-
6503 .sendUsdGH{usdGH, 100}, //
-
6504 .failUsdGH = STAmount{0}, //
-
6505 .failUsdGHr = STAmount{0}, //
-
6506 .failUsdBIT{usdBIT, uint64_t(11), 0}, //
-
6507 .failUsdBITr{usdBIT, uint64_t(11), 0}, //
-
6508 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
-
6509 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
-
6510 .goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
-
6511 .goodUsdBITr{usdBIT, uint64_t(2'821579689703954), -15}, //
-
6512 .lpTokenBalance{10, 0}, //
-
6513 .offer1BtcGH = 1e-5, //
-
6514 .offer2BtcGH = 1, //
-
6515 .offer2UsdGH = 1e-5, //
-
6516 .rateBIT = 0, //
-
6517 .rateGH = 0, //
-
6518 },
-
6519 InputSet{
-
6520 .testCase = "Overflow test {50, 100, 5.55}", //
-
6521 .poolUsdBIT = 50, //
-
6522 .poolUsdGH = 100, //
-
6523 .sendMaxUsdBIT{usdBIT(5.55)}, //
-
6524 .sendUsdGH{usdGH, 100}, //
-
6525 .failUsdGH = STAmount{0}, //
-
6526 .failUsdGHr = STAmount{0}, //
-
6527 .failUsdBIT{usdBIT, uint64_t(55'55), -2}, //
-
6528 .failUsdBITr{usdBIT, uint64_t(55'55), -2}, //
-
6529 .goodUsdGH{usdGH, uint64_t(90'04347888284113), -14}, //
-
6530 .goodUsdGHr{usdGH, uint64_t(90'0434788828413), -13}, //
-
6531 .goodUsdBIT{usdBIT, uint64_t(55'55), -2}, //
-
6532 .goodUsdBITr{usdBIT, uint64_t(55'55), -2}, //
-
6533 .lpTokenBalance{uint64_t(70'71067811865475), -14}, //
-
6534 .offer1BtcGH = 1e-5, //
-
6535 .offer2BtcGH = 1, //
-
6536 .offer2UsdGH = 1e-5, //
-
6537 .rateBIT = 0, //
-
6538 .rateGH = 0, //
-
6539 },
-
6540 InputSet{
-
6541 .testCase = "Overflow test {50, 100, 50.00}", //
-
6542 .poolUsdBIT = 50, //
-
6543 .poolUsdGH = 100, //
-
6544 .sendMaxUsdBIT{usdBIT(50.00)}, //
-
6545 .sendUsdGH{usdGH, 100}, //
-
6546 .failUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
-
6547 .failUsdGHr{usdGH, uint64_t(52'94379354424092), -14}, //
-
6548 .failUsdBIT{usdBIT, uint64_t(100), 0}, //
-
6549 .failUsdBITr{usdBIT, uint64_t(100), 0}, //
-
6550 .goodUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
-
6551 .goodUsdGHr{usdGH, uint64_t(52'94379354424092), -14}, //
-
6552 .goodUsdBIT{usdBIT, uint64_t(100), 0}, //
-
6553 .goodUsdBITr{usdBIT, uint64_t(100), 0}, //
-
6554 .lpTokenBalance{uint64_t(70'71067811865475), -14}, //
-
6555 .offer1BtcGH = 1e-5, //
-
6556 .offer2BtcGH = 1, //
-
6557 .offer2UsdGH = 1e-5, //
-
6558 .rateBIT = 0, //
-
6559 .rateGH = 0, //
-
6560 },
-
6561 InputSet{
-
6562 .testCase = "Overflow test {50, 100, 232.16}", //
-
6563 .poolUsdBIT = 50, //
-
6564 .poolUsdGH = 100, //
-
6565 .sendMaxUsdBIT{usdBIT(232.16)}, //
-
6566 .sendUsdGH{usdGH, 100}, //
-
6567 .failUsdGH = STAmount{0}, //
-
6568 .failUsdGHr = STAmount{0}, //
-
6569 .failUsdBIT{usdBIT, uint64_t(282'16), -2}, //
-
6570 .failUsdBITr{usdBIT, uint64_t(282'16), -2}, //
-
6571 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
-
6572 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
-
6573 .goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
-
6574 .goodUsdBITr{usdBIT, uint64_t(141'0789844851962), -13}, //
-
6575 .lpTokenBalance{70'71067811865475, -14}, //
-
6576 .offer1BtcGH = 1e-5, //
-
6577 .offer2BtcGH = 1, //
-
6578 .offer2UsdGH = 1e-5, //
-
6579 .rateBIT = 0, //
-
6580 .rateGH = 0, //
-
6581 },
-
6582 InputSet{
-
6583 .testCase = "Overflow test {50, 100, 500}", //
-
6584 .poolUsdBIT = 50, //
-
6585 .poolUsdGH = 100, //
-
6586 .sendMaxUsdBIT{usdBIT(500)}, //
-
6587 .sendUsdGH{usdGH, 100}, //
-
6588 .failUsdGH = STAmount{0}, //
-
6589 .failUsdGHr = STAmount{0}, //
-
6590 .failUsdBIT{usdBIT, uint64_t(550), 0}, //
-
6591 .failUsdBITr{usdBIT, uint64_t(550), 0}, //
-
6592 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
-
6593 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
-
6594 .goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
-
6595 .goodUsdBITr{usdBIT, uint64_t(141'0789844851962), -13}, //
-
6596 .lpTokenBalance{70'71067811865475, -14}, //
-
6597 .offer1BtcGH = 1e-5, //
-
6598 .offer2BtcGH = 1, //
-
6599 .offer2UsdGH = 1e-5, //
-
6600 .rateBIT = 0, //
-
6601 .rateGH = 0, //
-
6602 },
-
6603 })
-
6604 {
-
6605 testcase(input.testCase);
-
6606 for (auto const& features :
-
6607 {all - fixAMMOverflowOffer, all | fixAMMOverflowOffer})
-
6608 {
-
6609 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
-
6610
-
6611 env.fund(XRP(5'000), gatehub, bitstamp, trader);
-
6612 env.close();
-
6613
-
6614 if (input.rateGH != 0.0)
-
6615 env(rate(gatehub, input.rateGH));
-
6616 if (input.rateBIT != 0.0)
-
6617 env(rate(bitstamp, input.rateBIT));
-
6618
-
6619 env(trust(trader, usdGH(10'000'000)));
-
6620 env(trust(trader, usdBIT(10'000'000)));
-
6621 env(trust(trader, btcGH(10'000'000)));
-
6622 env.close();
-
6623
-
6624 env(pay(gatehub, trader, usdGH(100'000)));
-
6625 env(pay(gatehub, trader, btcGH(100'000)));
-
6626 env(pay(bitstamp, trader, usdBIT(100'000)));
-
6627 env.close();
-
6628
-
6629 AMM amm{
-
6630 env,
-
6631 trader,
-
6632 usdGH(input.poolUsdGH),
-
6633 usdBIT(input.poolUsdBIT)};
-
6634 env.close();
-
6635
-
6636 IOUAmount const preSwapLPTokenBalance =
-
6637 amm.getLPTokensBalance();
-
6638
-
6639 env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
-
6640 env(offer(
-
6641 trader,
-
6642 btcGH(input.offer2BtcGH),
-
6643 usdGH(input.offer2UsdGH)));
-
6644 env.close();
-
6645
-
6646 env(pay(trader, trader, input.sendUsdGH),
-
6647 path(~usdGH),
-
6648 path(~btcGH, ~usdGH),
-
6649 sendmax(input.sendMaxUsdBIT),
-
6650 txflags(tfPartialPayment));
-
6651 env.close();
-
6652
-
6653 auto const failUsdGH =
-
6654 features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
-
6655 auto const failUsdBIT =
-
6656 features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
-
6657 auto const goodUsdGH =
-
6658 features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
-
6659 auto const goodUsdBIT =
-
6660 features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
-
6661 if (!features[fixAMMOverflowOffer])
-
6662 {
-
6663 BEAST_EXPECT(amm.expectBalances(
-
6664 failUsdGH, failUsdBIT, input.lpTokenBalance));
-
6665 }
-
6666 else
-
6667 {
-
6668 BEAST_EXPECT(amm.expectBalances(
-
6669 goodUsdGH, goodUsdBIT, input.lpTokenBalance));
-
6670
-
6671 // Invariant: LPToken balance must not change in a
-
6672 // payment or a swap transaction
-
6673 BEAST_EXPECT(
-
6674 amm.getLPTokensBalance() == preSwapLPTokenBalance);
-
6675
-
6676 // Invariant: The square root of (product of the pool
-
6677 // balances) must be at least the LPTokenBalance
-
6678 Number const sqrtPoolProduct =
-
6679 root2(goodUsdGH * goodUsdBIT);
+
6304 void
+
6305 testFixDefaultInnerObj()
+
6306 {
+
6307 testcase("Fix Default Inner Object");
+
6308 using namespace jtx;
+
6309 FeatureBitset const all{supported_amendments()};
+
6310
+
6311 auto test = [&](FeatureBitset features,
+
6312 TER const& err1,
+
6313 TER const& err2,
+
6314 TER const& err3,
+
6315 TER const& err4,
+
6316 std::uint16_t tfee,
+
6317 bool closeLedger,
+
6318 std::optional<std::uint16_t> extra = std::nullopt) {
+
6319 Env env(*this, features);
+
6320 fund(env, gw, {alice}, XRP(1'000), {USD(10)});
+
6321 AMM amm(
+
6322 env,
+
6323 gw,
+
6324 XRP(10),
+
6325 USD(10),
+
6326 {.tfee = tfee, .close = closeLedger});
+
6327 amm.deposit(alice, USD(10), XRP(10));
+
6328 amm.vote(VoteArg{.account = alice, .tfee = tfee, .err = ter(err1)});
+
6329 amm.withdraw(WithdrawArg{
+
6330 .account = gw, .asset1Out = USD(1), .err = ter(err2)});
+
6331 // with the amendment disabled and ledger not closed,
+
6332 // second vote succeeds if the first vote sets the trading fee
+
6333 // to non-zero; if the first vote sets the trading fee to >0 &&
+
6334 // <9 then the second withdraw succeeds if the second vote sets
+
6335 // the trading fee so that the discounted fee is non-zero
+
6336 amm.vote(VoteArg{.account = alice, .tfee = 20, .err = ter(err3)});
+
6337 amm.withdraw(WithdrawArg{
+
6338 .account = gw, .asset1Out = USD(2), .err = ter(err4)});
+
6339 };
+
6340
+
6341 // ledger is closed after each transaction, vote/withdraw don't fail
+
6342 // regardless whether the amendment is enabled or not
+
6343 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, true);
+
6344 test(
+
6345 all - fixInnerObjTemplate,
+
6346 tesSUCCESS,
+
6347 tesSUCCESS,
+
6348 tesSUCCESS,
+
6349 tesSUCCESS,
+
6350 0,
+
6351 true);
+
6352 // ledger is not closed after each transaction
+
6353 // vote/withdraw don't fail if the amendment is enabled
+
6354 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, false);
+
6355 // vote/withdraw fail if the amendment is not enabled
+
6356 // second vote/withdraw still fail: second vote fails because
+
6357 // the initial trading fee is 0, consequently second withdraw fails
+
6358 // because the second vote fails
+
6359 test(
+
6360 all - fixInnerObjTemplate,
+
6361 tefEXCEPTION,
+
6362 tefEXCEPTION,
+
6363 tefEXCEPTION,
+
6364 tefEXCEPTION,
+
6365 0,
+
6366 false);
+
6367 // if non-zero trading/discounted fee then vote/withdraw
+
6368 // don't fail whether the ledger is closed or not and
+
6369 // the amendment is enabled or not
+
6370 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, true);
+
6371 test(
+
6372 all - fixInnerObjTemplate,
+
6373 tesSUCCESS,
+
6374 tesSUCCESS,
+
6375 tesSUCCESS,
+
6376 tesSUCCESS,
+
6377 10,
+
6378 true);
+
6379 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, false);
+
6380 test(
+
6381 all - fixInnerObjTemplate,
+
6382 tesSUCCESS,
+
6383 tesSUCCESS,
+
6384 tesSUCCESS,
+
6385 tesSUCCESS,
+
6386 10,
+
6387 false);
+
6388 // non-zero trading fee but discounted fee is 0, vote doesn't fail
+
6389 // but withdraw fails
+
6390 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 9, false);
+
6391 // second vote sets the trading fee to non-zero, consequently
+
6392 // second withdraw doesn't fail even if the amendment is not
+
6393 // enabled and the ledger is not closed
+
6394 test(
+
6395 all - fixInnerObjTemplate,
+
6396 tesSUCCESS,
+
6397 tefEXCEPTION,
+
6398 tesSUCCESS,
+
6399 tesSUCCESS,
+
6400 9,
+
6401 false);
+
6402 }
+
6403
+
6404 void
+
6405 testFixChangeSpotPriceQuality(FeatureBitset features)
+
6406 {
+
6407 testcase("Fix changeSpotPriceQuality");
+
6408 using namespace jtx;
+
6409
+
6410 std::string logs;
+
6411
+
6412 enum class Status {
+
6413 SucceedShouldSucceedResize, // Succeed in pre-fix because
+
6414 // error allowance, succeed post-fix
+
6415 // because of offer resizing
+
6416 FailShouldSucceed, // Fail in pre-fix due to rounding,
+
6417 // succeed after fix because of XRP
+
6418 // side is generated first
+
6419 SucceedShouldFail, // Succeed in pre-fix, fail after fix
+
6420 // due to small quality difference
+
6421 Fail, // Both fail because the quality can't be matched
+
6422 Succeed // Both succeed
+
6423 };
+
6424 using enum Status;
+
6425 auto const xrpIouAmounts10_100 =
+
6426 TAmounts{XRPAmount{10}, IOUAmount{100}};
+
6427 auto const iouXrpAmounts10_100 =
+
6428 TAmounts{IOUAmount{10}, XRPAmount{100}};
+
6429 // clang-format off
+
6430 std::vector<std::tuple<std::string, std::string, Quality, std::uint16_t, Status>> tests = {
+
6431 //Pool In , Pool Out, Quality , Fee, Status
+
6432 {"0.001519763260828713", "1558701", Quality{5414253689393440221}, 1000, FailShouldSucceed},
+
6433 {"0.01099814367603737", "1892611", Quality{5482264816516900274}, 1000, FailShouldSucceed},
+
6434 {"0.78", "796599", Quality{5630392334958379008}, 1000, FailShouldSucceed},
+
6435 {"105439.2955578965", "49398693", Quality{5910869983721805038}, 400, FailShouldSucceed},
+
6436 {"12408293.23445213", "4340810521", Quality{5911611095910090752}, 997, FailShouldSucceed},
+
6437 {"1892611", "0.01099814367603737", Quality{6703103457950430139}, 1000, FailShouldSucceed},
+
6438 {"423028.8508101858", "3392804520", Quality{5837920340654162816}, 600, FailShouldSucceed},
+
6439 {"44565388.41001027", "73890647", Quality{6058976634606450001}, 1000, FailShouldSucceed},
+
6440 {"66831.68494832662", "16", Quality{6346111134641742975}, 0, FailShouldSucceed},
+
6441 {"675.9287302203422", "1242632304", Quality{5625960929244093294}, 300, FailShouldSucceed},
+
6442 {"7047.112186735699", "1649845866", Quality{5696855348026306945}, 504, FailShouldSucceed},
+
6443 {"840236.4402981238", "47419053", Quality{5982561601648018688}, 499, FailShouldSucceed},
+
6444 {"992715.618909774", "189445631733", Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
+
6445 {"504636667521", "185545883.9506651", Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
+
6446 {"992706.7218636649", "189447316000", Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
+
6447 {"1.068737911388205", "127860278877", Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
+
6448 {"17932506.56880419", "189308.6043676173", Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
+
6449 {"1.066379294658174", "128042251493", Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
+
6450 {"350131413924", "1576879.110907892", Quality{6487411636539049449}, 650, Fail},
+
6451 {"422093460", "2.731797662057464", Quality{6702911108534394924}, 1000, Fail},
+
6452 {"76128132223", "367172.7148422662", Quality{6487263463413514240}, 548, Fail},
+
6453 {"132701839250", "280703770.7695443", Quality{6273750681188885075}, 562, Fail},
+
6454 {"994165.7604612011", "189551302411", Quality{5697835592690668727}, 815, Fail},
+
6455 {"45053.33303227917", "86612695359", Quality{5625695218943638190}, 500, Fail},
+
6456 {"199649.077043865", "14017933007", Quality{5766034667318524880}, 324, Fail},
+
6457 {"27751824831.70903", "78896950", Quality{6272538159621630432}, 500, Fail},
+
6458 {"225.3731275781907", "156431793648", Quality{5477818047604078924}, 989, Fail},
+
6459 {"199649.077043865", "14017933007", Quality{5766036094462806309}, 324, Fail},
+
6460 {"3.590272027140361", "20677643641", Quality{5406056147042156356}, 808, Fail},
+
6461 {"1.070884664490231", "127604712776", Quality{5268620608623825741}, 293, Fail},
+
6462 {"3272.448829820197", "6275124076", Quality{5625710328924117902}, 81, Fail},
+
6463 {"0.009059512633902926", "7994028", Quality{5477511954775533172}, 1000, Fail},
+
6464 {"1", "1.0", Quality{0}, 100, Fail},
+
6465 {"1.0", "1", Quality{0}, 100, Fail},
+
6466 {"10", "10.0", Quality{xrpIouAmounts10_100}, 100, Fail},
+
6467 {"10.0", "10", Quality{iouXrpAmounts10_100}, 100, Fail},
+
6468 {"69864389131", "287631.4543025075", Quality{6487623473313516078}, 451, Succeed},
+
6469 {"4328342973", "12453825.99247381", Quality{6272522264364865181}, 997, Succeed},
+
6470 {"32347017", "7003.93031579449", Quality{6347261126087916670}, 1000, Succeed},
+
6471 {"61697206161", "36631.4583206413", Quality{6558965195382476659}, 500, Succeed},
+
6472 {"1654524979", "7028.659825511603", Quality{6487551345110052981}, 504, Succeed},
+
6473 {"88621.22277293179", "5128418948", Quality{5766347291552869205}, 380, Succeed},
+
6474 {"1892611", "0.01099814367603737", Quality{6703102780512015436}, 1000, Succeed},
+
6475 {"4542.639373338766", "24554809", Quality{5838994982188783710}, 0, Succeed},
+
6476 {"5132932546", "88542.99750172683", Quality{6419203342950054537}, 380, Succeed},
+
6477 {"78929964.1549083", "1506494795", Quality{5986890029845558688}, 589, Succeed},
+
6478 {"10096561906", "44727.72453735605", Quality{6487455290284644551}, 250, Succeed},
+
6479 {"5092.219565514988", "8768257694", Quality{5626349534958379008}, 503, Succeed},
+
6480 {"1819778294", "8305.084302902864", Quality{6487429398998540860}, 415, Succeed},
+
6481 {"6970462.633911943", "57359281", Quality{6054087899185946624}, 850, Succeed},
+
6482 {"3983448845", "2347.543644281467", Quality{6558965195382476659}, 856, Succeed},
+
6483 // This is a tiny offer 12drops/19321952e-15 it succeeds pre-amendment because of the error allowance.
+
6484 // Post amendment it is resized to 11drops/17711789e-15 but the quality is still less than
+
6485 // the target quality and the offer fails.
+
6486 {"771493171", "1.243473020567508", Quality{6707566798038544272}, 100, SucceedShouldFail},
+
6487 };
+
6488 // clang-format on
+
6489
+
6490 boost::regex rx("^\\d+$");
+
6491 boost::smatch match;
+
6492 // tests that succeed should have the same amounts pre-fix and post-fix
+
6493 std::vector<std::pair<STAmount, STAmount>> successAmounts;
+
6494 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
+
6495 auto rules = env.current()->rules();
+
6496 CurrentTransactionRulesGuard rg(rules);
+
6497 for (auto const& t : tests)
+
6498 {
+
6499 auto getPool = [&](std::string const& v, bool isXRP) {
+
6500 if (isXRP)
+
6501 return amountFromString(xrpIssue(), v);
+
6502 return amountFromString(noIssue(), v);
+
6503 };
+
6504 auto const& quality = std::get<Quality>(t);
+
6505 auto const tfee = std::get<std::uint16_t>(t);
+
6506 auto const status = std::get<Status>(t);
+
6507 auto const poolInIsXRP =
+
6508 boost::regex_search(std::get<0>(t), match, rx);
+
6509 auto const poolOutIsXRP =
+
6510 boost::regex_search(std::get<1>(t), match, rx);
+
6511 assert(!(poolInIsXRP && poolOutIsXRP));
+
6512 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
+
6513 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
+
6514 try
+
6515 {
+
6516 auto const amounts = changeSpotPriceQuality(
+
6517 Amounts{poolIn, poolOut},
+
6518 quality,
+
6519 tfee,
+
6520 env.current()->rules(),
+
6521 env.journal);
+
6522 if (amounts)
+
6523 {
+
6524 if (status == SucceedShouldSucceedResize)
+
6525 {
+
6526 if (!features[fixAMMv1_1])
+
6527 BEAST_EXPECT(Quality{*amounts} < quality);
+
6528 else
+
6529 BEAST_EXPECT(Quality{*amounts} >= quality);
+
6530 }
+
6531 else if (status == Succeed)
+
6532 {
+
6533 if (!features[fixAMMv1_1])
+
6534 BEAST_EXPECT(
+
6535 Quality{*amounts} >= quality ||
+
6536 withinRelativeDistance(
+
6537 Quality{*amounts}, quality, Number{1, -7}));
+
6538 else
+
6539 BEAST_EXPECT(Quality{*amounts} >= quality);
+
6540 }
+
6541 else if (status == FailShouldSucceed)
+
6542 {
+
6543 BEAST_EXPECT(
+
6544 features[fixAMMv1_1] &&
+
6545 Quality{*amounts} >= quality);
+
6546 }
+
6547 else if (status == SucceedShouldFail)
+
6548 {
+
6549 BEAST_EXPECT(
+
6550 !features[fixAMMv1_1] &&
+
6551 Quality{*amounts} < quality &&
+
6552 withinRelativeDistance(
+
6553 Quality{*amounts}, quality, Number{1, -7}));
+
6554 }
+
6555 }
+
6556 else
+
6557 {
+
6558 // Fails pre- and post-amendment because the quality can't
+
6559 // be matched. Verify by generating a tiny offer, which
+
6560 // doesn't match the quality. Exclude zero quality since
+
6561 // no offer is generated in this case.
+
6562 if (status == Fail && quality != Quality{0})
+
6563 {
+
6564 auto tinyOffer = [&]() {
+
6565 if (isXRP(poolIn))
+
6566 {
+
6567 auto const takerPays = STAmount{xrpIssue(), 1};
+
6568 return Amounts{
+
6569 takerPays,
+
6570 swapAssetIn(
+
6571 Amounts{poolIn, poolOut},
+
6572 takerPays,
+
6573 tfee)};
+
6574 }
+
6575 else if (isXRP(poolOut))
+
6576 {
+
6577 auto const takerGets = STAmount{xrpIssue(), 1};
+
6578 return Amounts{
+
6579 swapAssetOut(
+
6580 Amounts{poolIn, poolOut},
+
6581 takerGets,
+
6582 tfee),
+
6583 takerGets};
+
6584 }
+
6585 auto const takerPays = toAmount<STAmount>(
+
6586 getIssue(poolIn), Number{1, -10} * poolIn);
+
6587 return Amounts{
+
6588 takerPays,
+
6589 swapAssetIn(
+
6590 Amounts{poolIn, poolOut}, takerPays, tfee)};
+
6591 }();
+
6592 BEAST_EXPECT(Quality(tinyOffer) < quality);
+
6593 }
+
6594 else if (status == FailShouldSucceed)
+
6595 {
+
6596 BEAST_EXPECT(!features[fixAMMv1_1]);
+
6597 }
+
6598 else if (status == SucceedShouldFail)
+
6599 {
+
6600 BEAST_EXPECT(features[fixAMMv1_1]);
+
6601 }
+
6602 }
+
6603 }
+
6604 catch (std::runtime_error const& e)
+
6605 {
+
6606 BEAST_EXPECT(
+
6607 !strcmp(e.what(), "changeSpotPriceQuality failed"));
+
6608 BEAST_EXPECT(
+
6609 !features[fixAMMv1_1] && status == FailShouldSucceed);
+
6610 }
+
6611 }
+
6612
+
6613 // Test negative discriminant
+
6614 {
+
6615 // b**2 - 4 * a * c -> 1 * 1 - 4 * 1 * 1 = -3
+
6616 auto const res =
+
6617 solveQuadraticEqSmallest(Number{1}, Number{1}, Number{1});
+
6618 BEAST_EXPECT(!res.has_value());
+
6619 }
+
6620 }
+
6621
+
6622 void
+
6623 testMalformed()
+
6624 {
+
6625 using namespace jtx;
+
6626
+
6627 testAMM([&](AMM& ammAlice, Env& env) {
+
6628 WithdrawArg args{
+
6629 .flags = tfSingleAsset,
+
6630 .err = ter(temMALFORMED),
+
6631 };
+
6632 ammAlice.withdraw(args);
+
6633 });
+
6634
+
6635 testAMM([&](AMM& ammAlice, Env& env) {
+
6636 WithdrawArg args{
+
6637 .flags = tfOneAssetLPToken,
+
6638 .err = ter(temMALFORMED),
+
6639 };
+
6640 ammAlice.withdraw(args);
+
6641 });
+
6642
+
6643 testAMM([&](AMM& ammAlice, Env& env) {
+
6644 WithdrawArg args{
+
6645 .flags = tfLimitLPToken,
+
6646 .err = ter(temMALFORMED),
+
6647 };
+
6648 ammAlice.withdraw(args);
+
6649 });
+
6650
+
6651 testAMM([&](AMM& ammAlice, Env& env) {
+
6652 WithdrawArg args{
+
6653 .asset1Out = XRP(100),
+
6654 .asset2Out = XRP(100),
+
6655 .err = ter(temBAD_AMM_TOKENS),
+
6656 };
+
6657 ammAlice.withdraw(args);
+
6658 });
+
6659
+
6660 testAMM([&](AMM& ammAlice, Env& env) {
+
6661 WithdrawArg args{
+
6662 .asset1Out = XRP(100),
+
6663 .asset2Out = BAD(100),
+
6664 .err = ter(temBAD_CURRENCY),
+
6665 };
+
6666 ammAlice.withdraw(args);
+
6667 });
+
6668
+
6669 testAMM([&](AMM& ammAlice, Env& env) {
+
6670 Json::Value jv;
+
6671 jv[jss::TransactionType] = jss::AMMWithdraw;
+
6672 jv[jss::Flags] = tfLimitLPToken;
+
6673 jv[jss::Account] = alice.human();
+
6674 ammAlice.setTokens(jv);
+
6675 XRP(100).value().setJson(jv[jss::Amount]);
+
6676 USD(100).value().setJson(jv[jss::EPrice]);
+
6677 env(jv, ter(temBAD_AMM_TOKENS));
+
6678 });
+
6679 }
6680
-
6681 // Include a tiny tolerance for the test cases using
-
6682 // .goodUsdGH{usdGH, uint64_t(35'44113971506987),
-
6683 // -14}, .goodUsdBIT{usdBIT,
-
6684 // uint64_t(2'821579689703915), -15},
-
6685 // These two values multiply
-
6686 // to 99.99999999999994227040383754105 which gets
-
6687 // internally rounded to 100, due to representation
-
6688 // error.
-
6689 BEAST_EXPECT(
-
6690 (sqrtPoolProduct + Number{1, -14} >=
-
6691 input.lpTokenBalance));
-
6692 }
-
6693 }
-
6694 }
-
6695 }
+
6681 void
+
6682 testFixOverflowOffer(FeatureBitset featuresInitial)
+
6683 {
+
6684 using namespace jtx;
+
6685 using namespace std::chrono;
+
6686 FeatureBitset const all{featuresInitial};
+
6687
+
6688 std::string logs;
+
6689
+
6690 Account const gatehub{"gatehub"};
+
6691 Account const bitstamp{"bitstamp"};
+
6692 Account const trader{"trader"};
+
6693 auto const usdGH = gatehub["USD"];
+
6694 auto const btcGH = gatehub["BTC"];
+
6695 auto const usdBIT = bitstamp["USD"];
6696
-
6697 void
-
6698 testSwapRounding()
-
6699 {
-
6700 testcase("swapRounding");
-
6701 using namespace jtx;
-
6702
-
6703 STAmount const xrpPool{XRP, UINT64_C(51600'000981)};
-
6704 STAmount const iouPool{USD, UINT64_C(803040'9987141784), -10};
-
6705
-
6706 STAmount const xrpBob{XRP, UINT64_C(1092'878933)};
-
6707 STAmount const iouBob{
-
6708 USD, UINT64_C(3'988035892323031), -28}; // 3.9...e-13
-
6709
-
6710 testAMM(
-
6711 [&](AMM& amm, Env& env) {
-
6712 // Check our AMM starting conditions.
-
6713 auto [xrpBegin, iouBegin, lptBegin] = amm.balances(XRP, USD);
-
6714
-
6715 // Set Bob's starting conditions.
-
6716 env.fund(xrpBob, bob);
-
6717 env.trust(USD(1'000'000), bob);
-
6718 env(pay(gw, bob, iouBob));
-
6719 env.close();
+
6697 struct InputSet
+
6698 {
+
6699 char const* testCase;
+
6700 double const poolUsdBIT;
+
6701 double const poolUsdGH;
+
6702 sendmax const sendMaxUsdBIT;
+
6703 STAmount const sendUsdGH;
+
6704 STAmount const failUsdGH;
+
6705 STAmount const failUsdGHr;
+
6706 STAmount const failUsdBIT;
+
6707 STAmount const failUsdBITr;
+
6708 STAmount const goodUsdGH;
+
6709 STAmount const goodUsdGHr;
+
6710 STAmount const goodUsdBIT;
+
6711 STAmount const goodUsdBITr;
+
6712 IOUAmount const lpTokenBalance;
+
6713 std::optional<IOUAmount> const lpTokenBalanceAlt = {};
+
6714 double const offer1BtcGH = 0.1;
+
6715 double const offer2BtcGH = 0.1;
+
6716 double const offer2UsdGH = 1;
+
6717 double const rateBIT = 0.0;
+
6718 double const rateGH = 0.0;
+
6719 };
6720
-
6721 env(offer(bob, XRP(6300), USD(100'000)));
-
6722 env.close();
-
6723
-
6724 // Assert that AMM is unchanged.
-
6725 BEAST_EXPECT(
-
6726 amm.expectBalances(xrpBegin, iouBegin, amm.tokens()));
-
6727 },
-
6728 {{xrpPool, iouPool}},
-
6729 889,
-
6730 std::nullopt,
-
6731 {jtx::supported_amendments() | fixAMMv1_1});
-
6732 }
-
6733
-
6734 void
-
6735 testFixAMMOfferBlockedByLOB(FeatureBitset features)
-
6736 {
-
6737 testcase("AMM Offer Blocked By LOB");
-
6738 using namespace jtx;
-
6739
-
6740 // Low quality LOB offer blocks AMM liquidity
-
6741
-
6742 // USD/XRP crosses AMM
-
6743 {
-
6744 Env env(*this, features);
-
6745
-
6746 fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)});
-
6747 // This offer blocks AMM offer in pre-amendment
-
6748 env(offer(alice, XRP(1), USD(0.01)));
-
6749 env.close();
-
6750
-
6751 AMM amm(env, gw, XRP(200'000), USD(100'000));
-
6752
-
6753 // The offer doesn't cross AMM in pre-amendment code
-
6754 // It crosses AMM in post-amendment code
-
6755 env(offer(carol, USD(0.49), XRP(1)));
-
6756 env.close();
-
6757
-
6758 if (!features[fixAMMv1_1])
-
6759 {
-
6760 BEAST_EXPECT(amm.expectBalances(
-
6761 XRP(200'000), USD(100'000), amm.tokens()));
-
6762 BEAST_EXPECT(expectOffers(
-
6763 env, alice, 1, {{Amounts{XRP(1), USD(0.01)}}}));
-
6764 // Carol's offer is blocked by alice's offer
-
6765 BEAST_EXPECT(expectOffers(
-
6766 env, carol, 1, {{Amounts{USD(0.49), XRP(1)}}}));
-
6767 }
-
6768 else
-
6769 {
-
6770 BEAST_EXPECT(amm.expectBalances(
-
6771 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
-
6772 BEAST_EXPECT(expectOffers(
-
6773 env, alice, 1, {{Amounts{XRP(1), USD(0.01)}}}));
-
6774 // Carol's offer crosses AMM
-
6775 BEAST_EXPECT(expectOffers(env, carol, 0));
-
6776 }
-
6777 }
-
6778
-
6779 // There is no blocking offer, the same AMM liquidity is consumed
-
6780 // pre- and post-amendment.
-
6781 {
-
6782 Env env(*this, features);
-
6783
-
6784 fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)});
-
6785 // There is no blocking offer
-
6786 // env(offer(alice, XRP(1), USD(0.01)));
-
6787
-
6788 AMM amm(env, gw, XRP(200'000), USD(100'000));
-
6789
-
6790 // The offer crosses AMM
-
6791 env(offer(carol, USD(0.49), XRP(1)));
-
6792 env.close();
-
6793
-
6794 // The same result as with the blocking offer
-
6795 BEAST_EXPECT(amm.expectBalances(
-
6796 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
-
6797 // Carol's offer crosses AMM
-
6798 BEAST_EXPECT(expectOffers(env, carol, 0));
-
6799 }
-
6800
-
6801 // XRP/USD crosses AMM
-
6802 {
-
6803 Env env(*this, features);
-
6804 fund(env, gw, {alice, carol, bob}, XRP(10'000), {USD(1'000)});
-
6805
-
6806 // This offer blocks AMM offer in pre-amendment
-
6807 // It crosses AMM in post-amendment code
-
6808 env(offer(bob, USD(1), XRPAmount(500)));
-
6809 env.close();
-
6810 AMM amm(env, alice, XRP(1'000), USD(500));
-
6811 env(offer(carol, XRP(100), USD(55)));
-
6812 env.close();
-
6813 if (!features[fixAMMv1_1])
-
6814 {
-
6815 BEAST_EXPECT(
-
6816 amm.expectBalances(XRP(1'000), USD(500), amm.tokens()));
-
6817 BEAST_EXPECT(expectOffers(
-
6818 env, bob, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
-
6819 BEAST_EXPECT(expectOffers(
-
6820 env, carol, 1, {{Amounts{XRP(100), USD(55)}}}));
-
6821 }
-
6822 else
-
6823 {
-
6824 BEAST_EXPECT(amm.expectBalances(
-
6825 XRPAmount(909'090'909),
-
6826 STAmount{USD, UINT64_C(550'000000055), -9},
-
6827 amm.tokens()));
-
6828 BEAST_EXPECT(expectOffers(
-
6829 env,
-
6830 carol,
-
6831 1,
-
6832 {{Amounts{
-
6833 XRPAmount{9'090'909},
-
6834 STAmount{USD, 4'99999995, -8}}}}));
-
6835 BEAST_EXPECT(expectOffers(
-
6836 env, bob, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
-
6837 }
-
6838 }
-
6839
-
6840 // There is no blocking offer, the same AMM liquidity is consumed
-
6841 // pre- and post-amendment.
-
6842 {
-
6843 Env env(*this, features);
-
6844 fund(env, gw, {alice, carol, bob}, XRP(10'000), {USD(1'000)});
-
6845
-
6846 AMM amm(env, alice, XRP(1'000), USD(500));
-
6847 env(offer(carol, XRP(100), USD(55)));
-
6848 env.close();
-
6849 BEAST_EXPECT(amm.expectBalances(
-
6850 XRPAmount(909'090'909),
-
6851 STAmount{USD, UINT64_C(550'000000055), -9},
-
6852 amm.tokens()));
-
6853 BEAST_EXPECT(expectOffers(
-
6854 env,
-
6855 carol,
-
6856 1,
-
6857 {{Amounts{
-
6858 XRPAmount{9'090'909}, STAmount{USD, 4'99999995, -8}}}}));
-
6859 }
-
6860 }
-
6861
-
6862 void
-
6863 testLPTokenBalance(FeatureBitset features)
-
6864 {
-
6865 using namespace jtx;
-
6866
-
6867 // Last Liquidity Provider is the issuer of one token
-
6868 {
-
6869 Env env(*this, features);
-
6870 fund(
-
6871 env,
-
6872 gw,
-
6873 {alice, carol},
-
6874 XRP(1'000'000'000),
-
6875 {USD(1'000'000'000)});
-
6876 AMM amm(env, gw, XRP(2), USD(1));
-
6877 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
-
6878 amm.deposit(carol, IOUAmount{1'000'000});
-
6879 amm.withdrawAll(alice);
-
6880 amm.withdrawAll(carol);
-
6881 auto const lpToken = getAccountLines(
-
6882 env, gw, amm.lptIssue())[jss::lines][0u][jss::balance];
-
6883 auto const lpTokenBalance =
-
6884 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
-
6885 BEAST_EXPECT(
-
6886 lpToken == "1414.213562373095" &&
-
6887 lpTokenBalance == "1414.213562373");
-
6888 if (!features[fixAMMv1_1])
-
6889 {
-
6890 amm.withdrawAll(gw, std::nullopt, ter(tecAMM_BALANCE));
-
6891 BEAST_EXPECT(amm.ammExists());
-
6892 }
-
6893 else
-
6894 {
-
6895 amm.withdrawAll(gw);
-
6896 BEAST_EXPECT(!amm.ammExists());
-
6897 }
-
6898 }
-
6899
-
6900 // Last Liquidity Provider is the issuer of two tokens, or not
-
6901 // the issuer
-
6902 for (auto const& lp : {gw, bob})
-
6903 {
-
6904 Env env(*this, features);
-
6905 auto const ABC = gw["ABC"];
-
6906 fund(
-
6907 env,
-
6908 gw,
-
6909 {alice, carol, bob},
-
6910 XRP(1'000),
-
6911 {USD(1'000'000'000), ABC(1'000'000'000'000)});
-
6912 AMM amm(env, lp, ABC(2'000'000), USD(1));
-
6913 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
-
6914 amm.deposit(carol, IOUAmount{1'000'000});
-
6915 amm.withdrawAll(alice);
-
6916 amm.withdrawAll(carol);
-
6917 auto const lpToken = getAccountLines(
-
6918 env, lp, amm.lptIssue())[jss::lines][0u][jss::balance];
-
6919 auto const lpTokenBalance =
-
6920 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
-
6921 BEAST_EXPECT(
-
6922 lpToken == "1414.213562373095" &&
-
6923 lpTokenBalance == "1414.213562373");
-
6924 if (!features[fixAMMv1_1])
-
6925 {
-
6926 amm.withdrawAll(lp, std::nullopt, ter(tecAMM_BALANCE));
-
6927 BEAST_EXPECT(amm.ammExists());
-
6928 }
-
6929 else
-
6930 {
-
6931 amm.withdrawAll(lp);
-
6932 BEAST_EXPECT(!amm.ammExists());
-
6933 }
-
6934 }
-
6935
-
6936 // More than one Liquidity Provider
-
6937 // XRP/IOU
-
6938 {
-
6939 Env env(*this, features);
-
6940 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)});
-
6941 AMM amm(env, gw, XRP(10), USD(10));
-
6942 amm.deposit(alice, 1'000);
-
6943 auto res =
-
6944 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
-
6945 BEAST_EXPECT(res && !res.value());
-
6946 res =
-
6947 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
-
6948 BEAST_EXPECT(res && !res.value());
-
6949 }
-
6950 // IOU/IOU, issuer of both IOU
-
6951 {
-
6952 Env env(*this, features);
-
6953 fund(env, gw, {alice}, XRP(1'000), {USD(1'000), EUR(1'000)});
-
6954 AMM amm(env, gw, EUR(10), USD(10));
-
6955 amm.deposit(alice, 1'000);
-
6956 auto res =
-
6957 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
-
6958 BEAST_EXPECT(res && !res.value());
-
6959 res =
-
6960 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
-
6961 BEAST_EXPECT(res && !res.value());
-
6962 }
-
6963 // IOU/IOU, issuer of one IOU
-
6964 {
-
6965 Env env(*this, features);
-
6966 Account const gw1("gw1");
-
6967 auto const YAN = gw1["YAN"];
-
6968 fund(env, gw, {gw1}, XRP(1'000), {USD(1'000)});
-
6969 fund(env, gw1, {gw}, XRP(1'000), {YAN(1'000)}, Fund::IOUOnly);
-
6970 AMM amm(env, gw1, YAN(10), USD(10));
-
6971 amm.deposit(gw, 1'000);
-
6972 auto res =
-
6973 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
-
6974 BEAST_EXPECT(res && !res.value());
-
6975 res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw1);
-
6976 BEAST_EXPECT(res && !res.value());
-
6977 }
-
6978 }
-
6979
-
6980 void
-
6981 testAMMClawback(FeatureBitset features)
-
6982 {
-
6983 testcase("test clawback from AMM account");
-
6984 using namespace jtx;
+
6721 using uint64_t = std::uint64_t;
+
6722
+
6723 for (auto const& input : {
+
6724 InputSet{
+
6725 .testCase = "Test Fix Overflow Offer", //
+
6726 .poolUsdBIT = 3, //
+
6727 .poolUsdGH = 273, //
+
6728 .sendMaxUsdBIT{usdBIT(50)}, //
+
6729 .sendUsdGH{usdGH, uint64_t(272'455089820359), -12}, //
+
6730 .failUsdGH = STAmount{0}, //
+
6731 .failUsdGHr = STAmount{0}, //
+
6732 .failUsdBIT{usdBIT, uint64_t(46'47826086956522), -14}, //
+
6733 .failUsdBITr{usdBIT, uint64_t(46'47826086956521), -14}, //
+
6734 .goodUsdGH{usdGH, uint64_t(96'7543114220382), -13}, //
+
6735 .goodUsdGHr{usdGH, uint64_t(96'7543114222965), -13}, //
+
6736 .goodUsdBIT{usdBIT, uint64_t(8'464739069120721), -15}, //
+
6737 .goodUsdBITr{usdBIT, uint64_t(8'464739069098152), -15}, //
+
6738 .lpTokenBalance = {28'61817604250837, -14}, //
+
6739 .lpTokenBalanceAlt = IOUAmount{28'61817604250836, -14}, //
+
6740 .offer1BtcGH = 0.1, //
+
6741 .offer2BtcGH = 0.1, //
+
6742 .offer2UsdGH = 1, //
+
6743 .rateBIT = 1.15, //
+
6744 .rateGH = 1.2, //
+
6745 },
+
6746 InputSet{
+
6747 .testCase = "Overflow test {1, 100, 0.111}", //
+
6748 .poolUsdBIT = 1, //
+
6749 .poolUsdGH = 100, //
+
6750 .sendMaxUsdBIT{usdBIT(0.111)}, //
+
6751 .sendUsdGH{usdGH, 100}, //
+
6752 .failUsdGH = STAmount{0}, //
+
6753 .failUsdGHr = STAmount{0}, //
+
6754 .failUsdBIT{usdBIT, uint64_t(1'111), -3}, //
+
6755 .failUsdBITr{usdBIT, uint64_t(1'111), -3}, //
+
6756 .goodUsdGH{usdGH, uint64_t(90'04347888284115), -14}, //
+
6757 .goodUsdGHr{usdGH, uint64_t(90'04347888284201), -14}, //
+
6758 .goodUsdBIT{usdBIT, uint64_t(1'111), -3}, //
+
6759 .goodUsdBITr{usdBIT, uint64_t(1'111), -3}, //
+
6760 .lpTokenBalance{10, 0}, //
+
6761 .offer1BtcGH = 1e-5, //
+
6762 .offer2BtcGH = 1, //
+
6763 .offer2UsdGH = 1e-5, //
+
6764 .rateBIT = 0, //
+
6765 .rateGH = 0, //
+
6766 },
+
6767 InputSet{
+
6768 .testCase = "Overflow test {1, 100, 1.00}", //
+
6769 .poolUsdBIT = 1, //
+
6770 .poolUsdGH = 100, //
+
6771 .sendMaxUsdBIT{usdBIT(1.00)}, //
+
6772 .sendUsdGH{usdGH, 100}, //
+
6773 .failUsdGH = STAmount{0}, //
+
6774 .failUsdGHr = STAmount{0}, //
+
6775 .failUsdBIT{usdBIT, uint64_t(2), 0}, //
+
6776 .failUsdBITr{usdBIT, uint64_t(2), 0}, //
+
6777 .goodUsdGH{usdGH, uint64_t(52'94379354424079), -14}, //
+
6778 .goodUsdGHr{usdGH, uint64_t(52'94379354424135), -14}, //
+
6779 .goodUsdBIT{usdBIT, uint64_t(2), 0}, //
+
6780 .goodUsdBITr{usdBIT, uint64_t(2), 0}, //
+
6781 .lpTokenBalance{10, 0}, //
+
6782 .offer1BtcGH = 1e-5, //
+
6783 .offer2BtcGH = 1, //
+
6784 .offer2UsdGH = 1e-5, //
+
6785 .rateBIT = 0, //
+
6786 .rateGH = 0, //
+
6787 },
+
6788 InputSet{
+
6789 .testCase = "Overflow test {1, 100, 4.6432}", //
+
6790 .poolUsdBIT = 1, //
+
6791 .poolUsdGH = 100, //
+
6792 .sendMaxUsdBIT{usdBIT(4.6432)}, //
+
6793 .sendUsdGH{usdGH, 100}, //
+
6794 .failUsdGH = STAmount{0}, //
+
6795 .failUsdGHr = STAmount{0}, //
+
6796 .failUsdBIT{usdBIT, uint64_t(5'6432), -4}, //
+
6797 .failUsdBITr{usdBIT, uint64_t(5'6432), -4}, //
+
6798 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
+
6799 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
+
6800 .goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
+
6801 .goodUsdBITr{usdBIT, uint64_t(2'821579689703954), -15}, //
+
6802 .lpTokenBalance{10, 0}, //
+
6803 .offer1BtcGH = 1e-5, //
+
6804 .offer2BtcGH = 1, //
+
6805 .offer2UsdGH = 1e-5, //
+
6806 .rateBIT = 0, //
+
6807 .rateGH = 0, //
+
6808 },
+
6809 InputSet{
+
6810 .testCase = "Overflow test {1, 100, 10}", //
+
6811 .poolUsdBIT = 1, //
+
6812 .poolUsdGH = 100, //
+
6813 .sendMaxUsdBIT{usdBIT(10)}, //
+
6814 .sendUsdGH{usdGH, 100}, //
+
6815 .failUsdGH = STAmount{0}, //
+
6816 .failUsdGHr = STAmount{0}, //
+
6817 .failUsdBIT{usdBIT, uint64_t(11), 0}, //
+
6818 .failUsdBITr{usdBIT, uint64_t(11), 0}, //
+
6819 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
+
6820 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
+
6821 .goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
+
6822 .goodUsdBITr{usdBIT, uint64_t(2'821579689703954), -15}, //
+
6823 .lpTokenBalance{10, 0}, //
+
6824 .offer1BtcGH = 1e-5, //
+
6825 .offer2BtcGH = 1, //
+
6826 .offer2UsdGH = 1e-5, //
+
6827 .rateBIT = 0, //
+
6828 .rateGH = 0, //
+
6829 },
+
6830 InputSet{
+
6831 .testCase = "Overflow test {50, 100, 5.55}", //
+
6832 .poolUsdBIT = 50, //
+
6833 .poolUsdGH = 100, //
+
6834 .sendMaxUsdBIT{usdBIT(5.55)}, //
+
6835 .sendUsdGH{usdGH, 100}, //
+
6836 .failUsdGH = STAmount{0}, //
+
6837 .failUsdGHr = STAmount{0}, //
+
6838 .failUsdBIT{usdBIT, uint64_t(55'55), -2}, //
+
6839 .failUsdBITr{usdBIT, uint64_t(55'55), -2}, //
+
6840 .goodUsdGH{usdGH, uint64_t(90'04347888284113), -14}, //
+
6841 .goodUsdGHr{usdGH, uint64_t(90'0434788828413), -13}, //
+
6842 .goodUsdBIT{usdBIT, uint64_t(55'55), -2}, //
+
6843 .goodUsdBITr{usdBIT, uint64_t(55'55), -2}, //
+
6844 .lpTokenBalance{uint64_t(70'71067811865475), -14}, //
+
6845 .offer1BtcGH = 1e-5, //
+
6846 .offer2BtcGH = 1, //
+
6847 .offer2UsdGH = 1e-5, //
+
6848 .rateBIT = 0, //
+
6849 .rateGH = 0, //
+
6850 },
+
6851 InputSet{
+
6852 .testCase = "Overflow test {50, 100, 50.00}", //
+
6853 .poolUsdBIT = 50, //
+
6854 .poolUsdGH = 100, //
+
6855 .sendMaxUsdBIT{usdBIT(50.00)}, //
+
6856 .sendUsdGH{usdGH, 100}, //
+
6857 .failUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
+
6858 .failUsdGHr{usdGH, uint64_t(52'94379354424092), -14}, //
+
6859 .failUsdBIT{usdBIT, uint64_t(100), 0}, //
+
6860 .failUsdBITr{usdBIT, uint64_t(100), 0}, //
+
6861 .goodUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
+
6862 .goodUsdGHr{usdGH, uint64_t(52'94379354424092), -14}, //
+
6863 .goodUsdBIT{usdBIT, uint64_t(100), 0}, //
+
6864 .goodUsdBITr{usdBIT, uint64_t(100), 0}, //
+
6865 .lpTokenBalance{uint64_t(70'71067811865475), -14}, //
+
6866 .offer1BtcGH = 1e-5, //
+
6867 .offer2BtcGH = 1, //
+
6868 .offer2UsdGH = 1e-5, //
+
6869 .rateBIT = 0, //
+
6870 .rateGH = 0, //
+
6871 },
+
6872 InputSet{
+
6873 .testCase = "Overflow test {50, 100, 232.16}", //
+
6874 .poolUsdBIT = 50, //
+
6875 .poolUsdGH = 100, //
+
6876 .sendMaxUsdBIT{usdBIT(232.16)}, //
+
6877 .sendUsdGH{usdGH, 100}, //
+
6878 .failUsdGH = STAmount{0}, //
+
6879 .failUsdGHr = STAmount{0}, //
+
6880 .failUsdBIT{usdBIT, uint64_t(282'16), -2}, //
+
6881 .failUsdBITr{usdBIT, uint64_t(282'16), -2}, //
+
6882 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
+
6883 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
+
6884 .goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
+
6885 .goodUsdBITr{usdBIT, uint64_t(141'0789844851962), -13}, //
+
6886 .lpTokenBalance{70'71067811865475, -14}, //
+
6887 .offer1BtcGH = 1e-5, //
+
6888 .offer2BtcGH = 1, //
+
6889 .offer2UsdGH = 1e-5, //
+
6890 .rateBIT = 0, //
+
6891 .rateGH = 0, //
+
6892 },
+
6893 InputSet{
+
6894 .testCase = "Overflow test {50, 100, 500}", //
+
6895 .poolUsdBIT = 50, //
+
6896 .poolUsdGH = 100, //
+
6897 .sendMaxUsdBIT{usdBIT(500)}, //
+
6898 .sendUsdGH{usdGH, 100}, //
+
6899 .failUsdGH = STAmount{0}, //
+
6900 .failUsdGHr = STAmount{0}, //
+
6901 .failUsdBIT{usdBIT, uint64_t(550), 0}, //
+
6902 .failUsdBITr{usdBIT, uint64_t(550), 0}, //
+
6903 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
+
6904 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
+
6905 .goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
+
6906 .goodUsdBITr{usdBIT, uint64_t(141'0789844851962), -13}, //
+
6907 .lpTokenBalance{70'71067811865475, -14}, //
+
6908 .offer1BtcGH = 1e-5, //
+
6909 .offer2BtcGH = 1, //
+
6910 .offer2UsdGH = 1e-5, //
+
6911 .rateBIT = 0, //
+
6912 .rateGH = 0, //
+
6913 },
+
6914 })
+
6915 {
+
6916 testcase(input.testCase);
+
6917 for (auto const& features :
+
6918 {all - fixAMMOverflowOffer - fixAMMv1_1 - fixAMMv1_3, all})
+
6919 {
+
6920 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
+
6921
+
6922 env.fund(XRP(5'000), gatehub, bitstamp, trader);
+
6923 env.close();
+
6924
+
6925 if (input.rateGH != 0.0)
+
6926 env(rate(gatehub, input.rateGH));
+
6927 if (input.rateBIT != 0.0)
+
6928 env(rate(bitstamp, input.rateBIT));
+
6929
+
6930 env(trust(trader, usdGH(10'000'000)));
+
6931 env(trust(trader, usdBIT(10'000'000)));
+
6932 env(trust(trader, btcGH(10'000'000)));
+
6933 env.close();
+
6934
+
6935 env(pay(gatehub, trader, usdGH(100'000)));
+
6936 env(pay(gatehub, trader, btcGH(100'000)));
+
6937 env(pay(bitstamp, trader, usdBIT(100'000)));
+
6938 env.close();
+
6939
+
6940 AMM amm{
+
6941 env,
+
6942 trader,
+
6943 usdGH(input.poolUsdGH),
+
6944 usdBIT(input.poolUsdBIT)};
+
6945 env.close();
+
6946
+
6947 IOUAmount const preSwapLPTokenBalance =
+
6948 amm.getLPTokensBalance();
+
6949
+
6950 env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
+
6951 env(offer(
+
6952 trader,
+
6953 btcGH(input.offer2BtcGH),
+
6954 usdGH(input.offer2UsdGH)));
+
6955 env.close();
+
6956
+
6957 env(pay(trader, trader, input.sendUsdGH),
+
6958 path(~usdGH),
+
6959 path(~btcGH, ~usdGH),
+
6960 sendmax(input.sendMaxUsdBIT),
+
6961 txflags(tfPartialPayment));
+
6962 env.close();
+
6963
+
6964 auto const failUsdGH =
+
6965 features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
+
6966 auto const failUsdBIT =
+
6967 features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
+
6968 auto const goodUsdGH =
+
6969 features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
+
6970 auto const goodUsdBIT =
+
6971 features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
+
6972 auto const lpTokenBalance =
+
6973 env.enabled(fixAMMv1_3) && input.lpTokenBalanceAlt
+
6974 ? *input.lpTokenBalanceAlt
+
6975 : input.lpTokenBalance;
+
6976 if (!features[fixAMMOverflowOffer])
+
6977 {
+
6978 BEAST_EXPECT(amm.expectBalances(
+
6979 failUsdGH, failUsdBIT, lpTokenBalance));
+
6980 }
+
6981 else
+
6982 {
+
6983 BEAST_EXPECT(amm.expectBalances(
+
6984 goodUsdGH, goodUsdBIT, lpTokenBalance));
6985
-
6986 // Issuer has clawback enabled
-
6987 Env env(*this, features);
-
6988 env.fund(XRP(1'000), gw);
-
6989 env(fset(gw, asfAllowTrustLineClawback));
-
6990 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
-
6991 env.close();
-
6992
-
6993 // If featureAMMClawback is not enabled, AMMCreate is not allowed for
-
6994 // clawback-enabled issuer
-
6995 if (!features[featureAMMClawback])
-
6996 {
-
6997 AMM amm(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
-
6998 AMM amm1(env, alice, USD(100), XRP(100), ter(tecNO_PERMISSION));
-
6999 env(fclear(gw, asfAllowTrustLineClawback));
-
7000 env.close();
-
7001 // Can't be cleared
-
7002 AMM amm2(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
-
7003 }
-
7004 // If featureAMMClawback is enabled, AMMCreate is allowed for
-
7005 // clawback-enabled issuer. Clawback from the AMM Account is not
-
7006 // allowed, which will return tecAMM_ACCOUNT. We can only use
-
7007 // AMMClawback transaction to claw back from AMM Account.
-
7008 else
-
7009 {
-
7010 AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
-
7011 AMM amm1(env, alice, USD(100), XRP(200), ter(tecDUPLICATE));
-
7012
-
7013 // Construct the amount being clawed back using AMM account.
-
7014 // By doing this, we make the clawback transaction's Amount field's
-
7015 // subfield `issuer` to be the AMM account, which means
-
7016 // we are clawing back from an AMM account. This should return an
-
7017 // tecAMM_ACCOUNT error because regular Clawback transaction is not
-
7018 // allowed for clawing back from an AMM account. Please notice the
-
7019 // `issuer` subfield represents the account being clawed back, which
-
7020 // is confusing.
-
7021 Issue usd(USD.issue().currency, amm.ammAccount());
-
7022 auto amount = amountFromString(usd, "10");
-
7023 env(claw(gw, amount), ter(tecAMM_ACCOUNT));
-
7024 }
-
7025 }
-
7026
-
7027 void
-
7028 testAMMDepositWithFrozenAssets(FeatureBitset features)
-
7029 {
-
7030 testcase("test AMMDeposit with frozen assets");
-
7031 using namespace jtx;
-
7032
-
7033 // This lambda function is used to create trustlines
-
7034 // between gw and alice, and create an AMM account.
-
7035 // And also test the callback function.
-
7036 auto testAMMDeposit = [&](Env& env, std::function<void(AMM & amm)> cb) {
-
7037 env.fund(XRP(1'000), gw);
-
7038 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
-
7039 env.close();
-
7040 AMM amm(env, alice, XRP(100), USD(100), ter(tesSUCCESS));
-
7041 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
7042 cb(amm);
-
7043 };
-
7044
-
7045 // Deposit two assets, one of which is frozen,
-
7046 // then we should get tecFROZEN error.
-
7047 {
-
7048 Env env(*this, features);
-
7049 testAMMDeposit(env, [&](AMM& amm) {
-
7050 amm.deposit(
-
7051 alice,
-
7052 USD(100),
-
7053 XRP(100),
-
7054 std::nullopt,
-
7055 tfTwoAsset,
-
7056 ter(tecFROZEN));
-
7057 });
-
7058 }
-
7059
-
7060 // Deposit one asset, which is the frozen token,
-
7061 // then we should get tecFROZEN error.
-
7062 {
-
7063 Env env(*this, features);
-
7064 testAMMDeposit(env, [&](AMM& amm) {
-
7065 amm.deposit(
-
7066 alice,
-
7067 USD(100),
-
7068 std::nullopt,
-
7069 std::nullopt,
-
7070 tfSingleAsset,
-
7071 ter(tecFROZEN));
-
7072 });
-
7073 }
-
7074
-
7075 if (features[featureAMMClawback])
-
7076 {
-
7077 // Deposit one asset which is not the frozen token,
-
7078 // but the other asset is frozen. We should get tecFROZEN error
-
7079 // when feature AMMClawback is enabled.
-
7080 Env env(*this, features);
-
7081 testAMMDeposit(env, [&](AMM& amm) {
-
7082 amm.deposit(
-
7083 alice,
-
7084 XRP(100),
-
7085 std::nullopt,
-
7086 std::nullopt,
-
7087 tfSingleAsset,
-
7088 ter(tecFROZEN));
-
7089 });
-
7090 }
-
7091 else
-
7092 {
-
7093 // Deposit one asset which is not the frozen token,
-
7094 // but the other asset is frozen. We will get tecSUCCESS
-
7095 // when feature AMMClawback is not enabled.
-
7096 Env env(*this, features);
-
7097 testAMMDeposit(env, [&](AMM& amm) {
-
7098 amm.deposit(
-
7099 alice,
-
7100 XRP(100),
-
7101 std::nullopt,
-
7102 std::nullopt,
-
7103 tfSingleAsset,
-
7104 ter(tesSUCCESS));
-
7105 });
-
7106 }
-
7107 }
+
6986 // Invariant: LPToken balance must not change in a
+
6987 // payment or a swap transaction
+
6988 BEAST_EXPECT(
+
6989 amm.getLPTokensBalance() == preSwapLPTokenBalance);
+
6990
+
6991 // Invariant: The square root of (product of the pool
+
6992 // balances) must be at least the LPTokenBalance
+
6993 Number const sqrtPoolProduct =
+
6994 root2(goodUsdGH * goodUsdBIT);
+
6995
+
6996 // Include a tiny tolerance for the test cases using
+
6997 // .goodUsdGH{usdGH, uint64_t(35'44113971506987),
+
6998 // -14}, .goodUsdBIT{usdBIT,
+
6999 // uint64_t(2'821579689703915), -15},
+
7000 // These two values multiply
+
7001 // to 99.99999999999994227040383754105 which gets
+
7002 // internally rounded to 100, due to representation
+
7003 // error.
+
7004 BEAST_EXPECT(
+
7005 (sqrtPoolProduct + Number{1, -14} >=
+
7006 input.lpTokenBalance));
+
7007 }
+
7008 }
+
7009 }
+
7010 }
+
7011
+
7012 void
+
7013 testSwapRounding()
+
7014 {
+
7015 testcase("swapRounding");
+
7016 using namespace jtx;
+
7017
+
7018 STAmount const xrpPool{XRP, UINT64_C(51600'000981)};
+
7019 STAmount const iouPool{USD, UINT64_C(803040'9987141784), -10};
+
7020
+
7021 STAmount const xrpBob{XRP, UINT64_C(1092'878933)};
+
7022 STAmount const iouBob{
+
7023 USD, UINT64_C(3'988035892323031), -28}; // 3.9...e-13
+
7024
+
7025 testAMM(
+
7026 [&](AMM& amm, Env& env) {
+
7027 // Check our AMM starting conditions.
+
7028 auto [xrpBegin, iouBegin, lptBegin] = amm.balances(XRP, USD);
+
7029
+
7030 // Set Bob's starting conditions.
+
7031 env.fund(xrpBob, bob);
+
7032 env.trust(USD(1'000'000), bob);
+
7033 env(pay(gw, bob, iouBob));
+
7034 env.close();
+
7035
+
7036 env(offer(bob, XRP(6300), USD(100'000)));
+
7037 env.close();
+
7038
+
7039 // Assert that AMM is unchanged.
+
7040 BEAST_EXPECT(
+
7041 amm.expectBalances(xrpBegin, iouBegin, amm.tokens()));
+
7042 },
+
7043 {{xrpPool, iouPool}},
+
7044 889,
+
7045 std::nullopt,
+
7046 {jtx::supported_amendments() | fixAMMv1_1});
+
7047 }
+
7048
+
7049 void
+
7050 testFixAMMOfferBlockedByLOB(FeatureBitset features)
+
7051 {
+
7052 testcase("AMM Offer Blocked By LOB");
+
7053 using namespace jtx;
+
7054
+
7055 // Low quality LOB offer blocks AMM liquidity
+
7056
+
7057 // USD/XRP crosses AMM
+
7058 {
+
7059 Env env(*this, features);
+
7060
+
7061 fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)});
+
7062 // This offer blocks AMM offer in pre-amendment
+
7063 env(offer(alice, XRP(1), USD(0.01)));
+
7064 env.close();
+
7065
+
7066 AMM amm(env, gw, XRP(200'000), USD(100'000));
+
7067
+
7068 // The offer doesn't cross AMM in pre-amendment code
+
7069 // It crosses AMM in post-amendment code
+
7070 env(offer(carol, USD(0.49), XRP(1)));
+
7071 env.close();
+
7072
+
7073 if (!features[fixAMMv1_1])
+
7074 {
+
7075 BEAST_EXPECT(amm.expectBalances(
+
7076 XRP(200'000), USD(100'000), amm.tokens()));
+
7077 BEAST_EXPECT(expectOffers(
+
7078 env, alice, 1, {{Amounts{XRP(1), USD(0.01)}}}));
+
7079 // Carol's offer is blocked by alice's offer
+
7080 BEAST_EXPECT(expectOffers(
+
7081 env, carol, 1, {{Amounts{USD(0.49), XRP(1)}}}));
+
7082 }
+
7083 else
+
7084 {
+
7085 BEAST_EXPECT(amm.expectBalances(
+
7086 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
+
7087 BEAST_EXPECT(expectOffers(
+
7088 env, alice, 1, {{Amounts{XRP(1), USD(0.01)}}}));
+
7089 // Carol's offer crosses AMM
+
7090 BEAST_EXPECT(expectOffers(env, carol, 0));
+
7091 }
+
7092 }
+
7093
+
7094 // There is no blocking offer, the same AMM liquidity is consumed
+
7095 // pre- and post-amendment.
+
7096 {
+
7097 Env env(*this, features);
+
7098
+
7099 fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)});
+
7100 // There is no blocking offer
+
7101 // env(offer(alice, XRP(1), USD(0.01)));
+
7102
+
7103 AMM amm(env, gw, XRP(200'000), USD(100'000));
+
7104
+
7105 // The offer crosses AMM
+
7106 env(offer(carol, USD(0.49), XRP(1)));
+
7107 env.close();
7108
-
7109 void
-
7110 testFixReserveCheckOnWithdrawal(FeatureBitset features)
-
7111 {
-
7112 testcase("Fix Reserve Check On Withdrawal");
-
7113 using namespace jtx;
-
7114
-
7115 auto const err = features[fixAMMv1_2] ? ter(tecINSUFFICIENT_RESERVE)
-
7116 : ter(tesSUCCESS);
-
7117
-
7118 auto test = [&](auto&& cb) {
-
7119 Env env(*this, features);
-
7120 auto const starting_xrp =
-
7121 reserve(env, 2) + env.current()->fees().base * 5;
-
7122 env.fund(starting_xrp, gw);
-
7123 env.fund(starting_xrp, alice);
-
7124 env.trust(USD(2'000), alice);
-
7125 env.close();
-
7126 env(pay(gw, alice, USD(2'000)));
+
7109 // The same result as with the blocking offer
+
7110 BEAST_EXPECT(amm.expectBalances(
+
7111 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
+
7112 // Carol's offer crosses AMM
+
7113 BEAST_EXPECT(expectOffers(env, carol, 0));
+
7114 }
+
7115
+
7116 // XRP/USD crosses AMM
+
7117 {
+
7118 Env env(*this, features);
+
7119 fund(env, gw, {alice, carol, bob}, XRP(10'000), {USD(1'000)});
+
7120
+
7121 // This offer blocks AMM offer in pre-amendment
+
7122 // It crosses AMM in post-amendment code
+
7123 env(offer(bob, USD(1), XRPAmount(500)));
+
7124 env.close();
+
7125 AMM amm(env, alice, XRP(1'000), USD(500));
+
7126 env(offer(carol, XRP(100), USD(55)));
7127 env.close();
-
7128 AMM amm(env, gw, EUR(1'000), USD(1'000));
-
7129 amm.deposit(alice, USD(1));
-
7130 cb(amm);
-
7131 };
-
7132
-
7133 // Equal withdraw
-
7134 test([&](AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); });
-
7135
-
7136 // Equal withdraw with a limit
-
7137 test([&](AMM& amm) {
-
7138 amm.withdraw(WithdrawArg{
-
7139 .account = alice,
-
7140 .asset1Out = EUR(0.1),
-
7141 .asset2Out = USD(0.1),
-
7142 .err = err});
-
7143 amm.withdraw(WithdrawArg{
-
7144 .account = alice,
-
7145 .asset1Out = USD(0.1),
-
7146 .asset2Out = EUR(0.1),
-
7147 .err = err});
-
7148 });
-
7149
-
7150 // Single withdraw
-
7151 test([&](AMM& amm) {
-
7152 amm.withdraw(WithdrawArg{
-
7153 .account = alice, .asset1Out = EUR(0.1), .err = err});
-
7154 amm.withdraw(WithdrawArg{.account = alice, .asset1Out = USD(0.1)});
-
7155 });
-
7156 }
-
7157
-
7158 void
-
7159 testFailedPseudoAccount()
-
7160 {
-
7161 using namespace test::jtx;
-
7162
-
7163 auto const testCase = [&](std::string suffix, FeatureBitset features) {
-
7164 testcase("Failed pseudo-account allocation " + suffix);
-
7165 Env env{*this, features};
-
7166 env.fund(XRP(30'000), gw, alice);
-
7167 env.close();
-
7168 env(trust(alice, gw["USD"](30'000), 0));
-
7169 env(pay(gw, alice, USD(10'000)));
-
7170 env.close();
-
7171
-
7172 STAmount amount = XRP(10'000);
-
7173 STAmount amount2 = USD(10'000);
-
7174 auto const keylet = keylet::amm(amount.issue(), amount2.issue());
-
7175 for (int i = 0; i < 256; ++i)
-
7176 {
-
7177 AccountID const accountId =
-
7178 ripple::pseudoAccountAddress(*env.current(), keylet.key);
-
7179
-
7180 env(pay(env.master.id(), accountId, XRP(1000)),
-
7181 seq(autofill),
-
7182 fee(autofill),
-
7183 sig(autofill));
-
7184 }
-
7185
-
7186 AMM ammAlice(
-
7187 env,
-
7188 alice,
-
7189 amount,
-
7190 amount2,
-
7191 features[featureSingleAssetVault] ? ter{terADDRESS_COLLISION}
-
7192 : ter{tecDUPLICATE});
-
7193 };
-
7194
-
7195 testCase(
-
7196 "tecDUPLICATE", supported_amendments() - featureSingleAssetVault);
-
7197 testCase(
-
7198 "terADDRESS_COLLISION",
-
7199 supported_amendments() | featureSingleAssetVault);
-
7200 }
-
7201
-
7202 void
-
7203 run() override
-
7204 {
-
7205 FeatureBitset const all{jtx::supported_amendments()};
-
7206 testInvalidInstance();
-
7207 testInstanceCreate();
-
7208 testInvalidDeposit(all);
-
7209 testInvalidDeposit(all - featureAMMClawback);
-
7210 testDeposit();
-
7211 testInvalidWithdraw();
-
7212 testWithdraw();
-
7213 testInvalidFeeVote();
-
7214 testFeeVote();
-
7215 testInvalidBid();
-
7216 testBid(all);
-
7217 testBid(all - fixAMMv1_1);
-
7218 testInvalidAMMPayment();
-
7219 testBasicPaymentEngine(all);
-
7220 testBasicPaymentEngine(all - fixAMMv1_1);
-
7221 testBasicPaymentEngine(all - fixReducedOffersV2);
-
7222 testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2);
-
7223 testAMMTokens();
-
7224 testAmendment();
-
7225 testFlags();
-
7226 testRippling();
-
7227 testAMMAndCLOB(all);
-
7228 testAMMAndCLOB(all - fixAMMv1_1);
-
7229 testTradingFee(all);
-
7230 testTradingFee(all - fixAMMv1_1);
-
7231 testAdjustedTokens(all);
-
7232 testAdjustedTokens(all - fixAMMv1_1);
-
7233 testAutoDelete();
-
7234 testClawback();
-
7235 testAMMID();
-
7236 testSelection(all);
-
7237 testSelection(all - fixAMMv1_1);
-
7238 testFixDefaultInnerObj();
-
7239 testMalformed();
-
7240 testFixOverflowOffer(all);
-
7241 testFixOverflowOffer(all - fixAMMv1_1);
-
7242 testSwapRounding();
-
7243 testFixChangeSpotPriceQuality(all);
-
7244 testFixChangeSpotPriceQuality(all - fixAMMv1_1);
-
7245 testFixAMMOfferBlockedByLOB(all);
-
7246 testFixAMMOfferBlockedByLOB(all - fixAMMv1_1);
-
7247 testLPTokenBalance(all);
-
7248 testLPTokenBalance(all - fixAMMv1_1);
-
7249 testAMMClawback(all);
-
7250 testAMMClawback(all - featureAMMClawback);
-
7251 testAMMClawback(all - fixAMMv1_1 - featureAMMClawback);
-
7252 testAMMDepositWithFrozenAssets(all);
-
7253 testAMMDepositWithFrozenAssets(all - featureAMMClawback);
-
7254 testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
-
7255 testFixReserveCheckOnWithdrawal(all);
-
7256 testFixReserveCheckOnWithdrawal(all - fixAMMv1_2);
-
7257 testFailedPseudoAccount();
-
7258 }
-
7259};
-
7260
-
7261BEAST_DEFINE_TESTSUITE_PRIO(AMM, app, ripple, 1);
-
7262
-
7263} // namespace test
-
7264} // namespace ripple
+
7128 if (!features[fixAMMv1_1])
+
7129 {
+
7130 BEAST_EXPECT(
+
7131 amm.expectBalances(XRP(1'000), USD(500), amm.tokens()));
+
7132 BEAST_EXPECT(expectOffers(
+
7133 env, bob, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
+
7134 BEAST_EXPECT(expectOffers(
+
7135 env, carol, 1, {{Amounts{XRP(100), USD(55)}}}));
+
7136 }
+
7137 else
+
7138 {
+
7139 BEAST_EXPECT(amm.expectBalances(
+
7140 XRPAmount(909'090'909),
+
7141 STAmount{USD, UINT64_C(550'000000055), -9},
+
7142 amm.tokens()));
+
7143 BEAST_EXPECT(expectOffers(
+
7144 env,
+
7145 carol,
+
7146 1,
+
7147 {{Amounts{
+
7148 XRPAmount{9'090'909},
+
7149 STAmount{USD, 4'99999995, -8}}}}));
+
7150 BEAST_EXPECT(expectOffers(
+
7151 env, bob, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
+
7152 }
+
7153 }
+
7154
+
7155 // There is no blocking offer, the same AMM liquidity is consumed
+
7156 // pre- and post-amendment.
+
7157 {
+
7158 Env env(*this, features);
+
7159 fund(env, gw, {alice, carol, bob}, XRP(10'000), {USD(1'000)});
+
7160
+
7161 AMM amm(env, alice, XRP(1'000), USD(500));
+
7162 env(offer(carol, XRP(100), USD(55)));
+
7163 env.close();
+
7164 BEAST_EXPECT(amm.expectBalances(
+
7165 XRPAmount(909'090'909),
+
7166 STAmount{USD, UINT64_C(550'000000055), -9},
+
7167 amm.tokens()));
+
7168 BEAST_EXPECT(expectOffers(
+
7169 env,
+
7170 carol,
+
7171 1,
+
7172 {{Amounts{
+
7173 XRPAmount{9'090'909}, STAmount{USD, 4'99999995, -8}}}}));
+
7174 }
+
7175 }
+
7176
+
7177 void
+
7178 testLPTokenBalance(FeatureBitset features)
+
7179 {
+
7180 testcase("LPToken Balance");
+
7181 using namespace jtx;
+
7182
+
7183 // Last Liquidity Provider is the issuer of one token
+
7184 {
+
7185 std::string logs;
+
7186 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
+
7187 fund(
+
7188 env,
+
7189 gw,
+
7190 {alice, carol},
+
7191 XRP(1'000'000'000),
+
7192 {USD(1'000'000'000)});
+
7193 AMM amm(env, gw, XRP(2), USD(1));
+
7194 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
+
7195 amm.deposit(carol, IOUAmount{1'000'000});
+
7196 amm.withdrawAll(alice);
+
7197 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{0}));
+
7198 amm.withdrawAll(carol);
+
7199 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount{0}));
+
7200 auto const lpToken = getAccountLines(
+
7201 env, gw, amm.lptIssue())[jss::lines][0u][jss::balance];
+
7202 auto const lpTokenBalance =
+
7203 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
+
7204 BEAST_EXPECT(
+
7205 lpToken == "1414.213562373095" &&
+
7206 lpTokenBalance == "1414.213562373");
+
7207 if (!features[fixAMMv1_1])
+
7208 {
+
7209 amm.withdrawAll(gw, std::nullopt, ter(tecAMM_BALANCE));
+
7210 BEAST_EXPECT(amm.ammExists());
+
7211 }
+
7212 else
+
7213 {
+
7214 amm.withdrawAll(gw);
+
7215 BEAST_EXPECT(!amm.ammExists());
+
7216 }
+
7217 }
+
7218
+
7219 // Last Liquidity Provider is the issuer of two tokens, or not
+
7220 // the issuer
+
7221 for (auto const& lp : {gw, bob})
+
7222 {
+
7223 Env env(*this, features);
+
7224 auto const ABC = gw["ABC"];
+
7225 fund(
+
7226 env,
+
7227 gw,
+
7228 {alice, carol, bob},
+
7229 XRP(1'000),
+
7230 {USD(1'000'000'000), ABC(1'000'000'000'000)});
+
7231 AMM amm(env, lp, ABC(2'000'000), USD(1));
+
7232 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
+
7233 amm.deposit(carol, IOUAmount{1'000'000});
+
7234 amm.withdrawAll(alice);
+
7235 amm.withdrawAll(carol);
+
7236 auto const lpToken = getAccountLines(
+
7237 env, lp, amm.lptIssue())[jss::lines][0u][jss::balance];
+
7238 auto const lpTokenBalance =
+
7239 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
+
7240 BEAST_EXPECT(
+
7241 lpToken == "1414.213562373095" &&
+
7242 lpTokenBalance == "1414.213562373");
+
7243 if (!features[fixAMMv1_1])
+
7244 {
+
7245 amm.withdrawAll(lp, std::nullopt, ter(tecAMM_BALANCE));
+
7246 BEAST_EXPECT(amm.ammExists());
+
7247 }
+
7248 else
+
7249 {
+
7250 amm.withdrawAll(lp);
+
7251 BEAST_EXPECT(!amm.ammExists());
+
7252 }
+
7253 }
+
7254
+
7255 // More than one Liquidity Provider
+
7256 // XRP/IOU
+
7257 {
+
7258 Env env(*this, features);
+
7259 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)});
+
7260 AMM amm(env, gw, XRP(10), USD(10));
+
7261 amm.deposit(alice, 1'000);
+
7262 auto res =
+
7263 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
+
7264 BEAST_EXPECT(res && !res.value());
+
7265 res =
+
7266 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
+
7267 BEAST_EXPECT(res && !res.value());
+
7268 }
+
7269 // IOU/IOU, issuer of both IOU
+
7270 {
+
7271 Env env(*this, features);
+
7272 fund(env, gw, {alice}, XRP(1'000), {USD(1'000), EUR(1'000)});
+
7273 AMM amm(env, gw, EUR(10), USD(10));
+
7274 amm.deposit(alice, 1'000);
+
7275 auto res =
+
7276 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
+
7277 BEAST_EXPECT(res && !res.value());
+
7278 res =
+
7279 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
+
7280 BEAST_EXPECT(res && !res.value());
+
7281 }
+
7282 // IOU/IOU, issuer of one IOU
+
7283 {
+
7284 Env env(*this, features);
+
7285 Account const gw1("gw1");
+
7286 auto const YAN = gw1["YAN"];
+
7287 fund(env, gw, {gw1}, XRP(1'000), {USD(1'000)});
+
7288 fund(env, gw1, {gw}, XRP(1'000), {YAN(1'000)}, Fund::IOUOnly);
+
7289 AMM amm(env, gw1, YAN(10), USD(10));
+
7290 amm.deposit(gw, 1'000);
+
7291 auto res =
+
7292 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
+
7293 BEAST_EXPECT(res && !res.value());
+
7294 res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw1);
+
7295 BEAST_EXPECT(res && !res.value());
+
7296 }
+
7297 }
+
7298
+
7299 void
+
7300 testAMMClawback(FeatureBitset features)
+
7301 {
+
7302 testcase("test clawback from AMM account");
+
7303 using namespace jtx;
+
7304
+
7305 // Issuer has clawback enabled
+
7306 Env env(*this, features);
+
7307 env.fund(XRP(1'000), gw);
+
7308 env(fset(gw, asfAllowTrustLineClawback));
+
7309 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
+
7310 env.close();
+
7311
+
7312 // If featureAMMClawback is not enabled, AMMCreate is not allowed for
+
7313 // clawback-enabled issuer
+
7314 if (!features[featureAMMClawback])
+
7315 {
+
7316 AMM amm(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
+
7317 AMM amm1(env, alice, USD(100), XRP(100), ter(tecNO_PERMISSION));
+
7318 env(fclear(gw, asfAllowTrustLineClawback));
+
7319 env.close();
+
7320 // Can't be cleared
+
7321 AMM amm2(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
+
7322 }
+
7323 // If featureAMMClawback is enabled, AMMCreate is allowed for
+
7324 // clawback-enabled issuer. Clawback from the AMM Account is not
+
7325 // allowed, which will return tecAMM_ACCOUNT. We can only use
+
7326 // AMMClawback transaction to claw back from AMM Account.
+
7327 else
+
7328 {
+
7329 AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
+
7330 AMM amm1(env, alice, USD(100), XRP(200), ter(tecDUPLICATE));
+
7331
+
7332 // Construct the amount being clawed back using AMM account.
+
7333 // By doing this, we make the clawback transaction's Amount field's
+
7334 // subfield `issuer` to be the AMM account, which means
+
7335 // we are clawing back from an AMM account. This should return an
+
7336 // tecAMM_ACCOUNT error because regular Clawback transaction is not
+
7337 // allowed for clawing back from an AMM account. Please notice the
+
7338 // `issuer` subfield represents the account being clawed back, which
+
7339 // is confusing.
+
7340 Issue usd(USD.issue().currency, amm.ammAccount());
+
7341 auto amount = amountFromString(usd, "10");
+
7342 env(claw(gw, amount), ter(tecAMM_ACCOUNT));
+
7343 }
+
7344 }
+
7345
+
7346 void
+
7347 testAMMDepositWithFrozenAssets(FeatureBitset features)
+
7348 {
+
7349 testcase("test AMMDeposit with frozen assets");
+
7350 using namespace jtx;
+
7351
+
7352 // This lambda function is used to create trustlines
+
7353 // between gw and alice, and create an AMM account.
+
7354 // And also test the callback function.
+
7355 auto testAMMDeposit = [&](Env& env, std::function<void(AMM & amm)> cb) {
+
7356 env.fund(XRP(1'000), gw);
+
7357 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
+
7358 env.close();
+
7359 AMM amm(env, alice, XRP(100), USD(100), ter(tesSUCCESS));
+
7360 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
7361 cb(amm);
+
7362 };
+
7363
+
7364 // Deposit two assets, one of which is frozen,
+
7365 // then we should get tecFROZEN error.
+
7366 {
+
7367 Env env(*this, features);
+
7368 testAMMDeposit(env, [&](AMM& amm) {
+
7369 amm.deposit(
+
7370 alice,
+
7371 USD(100),
+
7372 XRP(100),
+
7373 std::nullopt,
+
7374 tfTwoAsset,
+
7375 ter(tecFROZEN));
+
7376 });
+
7377 }
+
7378
+
7379 // Deposit one asset, which is the frozen token,
+
7380 // then we should get tecFROZEN error.
+
7381 {
+
7382 Env env(*this, features);
+
7383 testAMMDeposit(env, [&](AMM& amm) {
+
7384 amm.deposit(
+
7385 alice,
+
7386 USD(100),
+
7387 std::nullopt,
+
7388 std::nullopt,
+
7389 tfSingleAsset,
+
7390 ter(tecFROZEN));
+
7391 });
+
7392 }
+
7393
+
7394 if (features[featureAMMClawback])
+
7395 {
+
7396 // Deposit one asset which is not the frozen token,
+
7397 // but the other asset is frozen. We should get tecFROZEN error
+
7398 // when feature AMMClawback is enabled.
+
7399 Env env(*this, features);
+
7400 testAMMDeposit(env, [&](AMM& amm) {
+
7401 amm.deposit(
+
7402 alice,
+
7403 XRP(100),
+
7404 std::nullopt,
+
7405 std::nullopt,
+
7406 tfSingleAsset,
+
7407 ter(tecFROZEN));
+
7408 });
+
7409 }
+
7410 else
+
7411 {
+
7412 // Deposit one asset which is not the frozen token,
+
7413 // but the other asset is frozen. We will get tecSUCCESS
+
7414 // when feature AMMClawback is not enabled.
+
7415 Env env(*this, features);
+
7416 testAMMDeposit(env, [&](AMM& amm) {
+
7417 amm.deposit(
+
7418 alice,
+
7419 XRP(100),
+
7420 std::nullopt,
+
7421 std::nullopt,
+
7422 tfSingleAsset,
+
7423 ter(tesSUCCESS));
+
7424 });
+
7425 }
+
7426 }
+
7427
+
7428 void
+
7429 testFixReserveCheckOnWithdrawal(FeatureBitset features)
+
7430 {
+
7431 testcase("Fix Reserve Check On Withdrawal");
+
7432 using namespace jtx;
+
7433
+
7434 auto const err = features[fixAMMv1_2] ? ter(tecINSUFFICIENT_RESERVE)
+
7435 : ter(tesSUCCESS);
+
7436
+
7437 auto test = [&](auto&& cb) {
+
7438 Env env(*this, features);
+
7439 auto const starting_xrp =
+
7440 reserve(env, 2) + env.current()->fees().base * 5;
+
7441 env.fund(starting_xrp, gw);
+
7442 env.fund(starting_xrp, alice);
+
7443 env.trust(USD(2'000), alice);
+
7444 env.close();
+
7445 env(pay(gw, alice, USD(2'000)));
+
7446 env.close();
+
7447 AMM amm(env, gw, EUR(1'000), USD(1'000));
+
7448 amm.deposit(alice, USD(1));
+
7449 cb(amm);
+
7450 };
+
7451
+
7452 // Equal withdraw
+
7453 test([&](AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); });
+
7454
+
7455 // Equal withdraw with a limit
+
7456 test([&](AMM& amm) {
+
7457 amm.withdraw(WithdrawArg{
+
7458 .account = alice,
+
7459 .asset1Out = EUR(0.1),
+
7460 .asset2Out = USD(0.1),
+
7461 .err = err});
+
7462 amm.withdraw(WithdrawArg{
+
7463 .account = alice,
+
7464 .asset1Out = USD(0.1),
+
7465 .asset2Out = EUR(0.1),
+
7466 .err = err});
+
7467 });
+
7468
+
7469 // Single withdraw
+
7470 test([&](AMM& amm) {
+
7471 amm.withdraw(WithdrawArg{
+
7472 .account = alice, .asset1Out = EUR(0.1), .err = err});
+
7473 amm.withdraw(WithdrawArg{.account = alice, .asset1Out = USD(0.1)});
+
7474 });
+
7475 }
+
7476
+
7477 void
+
7478 testFailedPseudoAccount()
+
7479 {
+
7480 using namespace test::jtx;
+
7481
+
7482 auto const testCase = [&](std::string suffix, FeatureBitset features) {
+
7483 testcase("Failed pseudo-account allocation " + suffix);
+
7484 std::string logs;
+
7485 Env env{*this, features, std::make_unique<CaptureLogs>(&logs)};
+
7486 env.fund(XRP(30'000), gw, alice);
+
7487 env.close();
+
7488 env(trust(alice, gw["USD"](30'000), 0));
+
7489 env(pay(gw, alice, USD(10'000)));
+
7490 env.close();
+
7491
+
7492 STAmount amount = XRP(10'000);
+
7493 STAmount amount2 = USD(10'000);
+
7494 auto const keylet = keylet::amm(amount.issue(), amount2.issue());
+
7495 for (int i = 0; i < 256; ++i)
+
7496 {
+
7497 AccountID const accountId =
+
7498 ripple::pseudoAccountAddress(*env.current(), keylet.key);
+
7499
+
7500 env(pay(env.master.id(), accountId, XRP(1000)),
+
7501 seq(autofill),
+
7502 fee(autofill),
+
7503 sig(autofill));
+
7504 }
+
7505
+
7506 AMM ammAlice(
+
7507 env,
+
7508 alice,
+
7509 amount,
+
7510 amount2,
+
7511 features[featureSingleAssetVault] ? ter{terADDRESS_COLLISION}
+
7512 : ter{tecDUPLICATE});
+
7513 };
+
7514
+
7515 testCase(
+
7516 "tecDUPLICATE", supported_amendments() - featureSingleAssetVault);
+
7517 testCase(
+
7518 "terADDRESS_COLLISION",
+
7519 supported_amendments() | featureSingleAssetVault);
+
7520 }
+
7521
+
7522 void
+
7523 testDepositAndWithdrawRounding(FeatureBitset features)
+
7524 {
+
7525 testcase("Deposit and Withdraw Rounding V2");
+
7526 using namespace jtx;
+
7527
+
7528 auto const XPM = gw["XPM"];
+
7529 STAmount xrpBalance{XRPAmount(692'614'492'126)};
+
7530 STAmount xpmBalance{XPM, UINT64_C(18'610'359'80246901), -8};
+
7531 STAmount amount{XPM, UINT64_C(6'566'496939465400), -12};
+
7532 std::uint16_t tfee = 941;
+
7533
+
7534 auto test = [&](auto&& cb, std::uint16_t tfee_) {
+
7535 Env env(*this, features);
+
7536 env.fund(XRP(1'000'000), gw);
+
7537 env.fund(XRP(1'000), alice);
+
7538 env(trust(alice, XPM(7'000)));
+
7539 env(pay(gw, alice, amount));
+
7540
+
7541 AMM amm(env, gw, xrpBalance, xpmBalance, CreateArg{.tfee = tfee_});
+
7542 // AMM LPToken balance required to replicate single deposit failure
+
7543 STAmount lptAMMBalance{
+
7544 amm.lptIssue(), UINT64_C(3'234'987'266'485968), -6};
+
7545 auto const burn =
+
7546 IOUAmount{amm.getLPTokensBalance() - lptAMMBalance};
+
7547 // burn tokens to get to the required AMM state
+
7548 env(amm.bid(BidArg{.account = gw, .bidMin = burn, .bidMax = burn}));
+
7549 cb(amm, env);
+
7550 };
+
7551 test(
+
7552 [&](AMM& amm, Env& env) {
+
7553 auto const err = env.enabled(fixAMMv1_3) ? ter(tesSUCCESS)
+
7554 : ter(tecUNFUNDED_AMM);
+
7555 amm.deposit(DepositArg{
+
7556 .account = alice, .asset1In = amount, .err = err});
+
7557 },
+
7558 tfee);
+
7559 test(
+
7560 [&](AMM& amm, Env& env) {
+
7561 auto const [amount, amount2, lptAMM] = amm.balances(XRP, XPM);
+
7562 auto const withdraw = STAmount{XPM, 1, -5};
+
7563 amm.withdraw(WithdrawArg{.asset1Out = STAmount{XPM, 1, -5}});
+
7564 auto const [amount_, amount2_, lptAMM_] =
+
7565 amm.balances(XRP, XPM);
+
7566 if (!env.enabled(fixAMMv1_3))
+
7567 BEAST_EXPECT((amount2 - amount2_) > withdraw);
+
7568 else
+
7569 BEAST_EXPECT((amount2 - amount2_) <= withdraw);
+
7570 },
+
7571 0);
+
7572 }
+
7573
+
7574 void
+
7575 invariant(
+
7576 jtx::AMM& amm,
+
7577 jtx::Env& env,
+
7578 std::string const& msg,
+
7579 bool shouldFail)
+
7580 {
+
7581 auto const [amount, amount2, lptBalance] = amm.balances(GBP, EUR);
+
7582
+
7583 NumberRoundModeGuard g(
+
7584 env.enabled(fixAMMv1_3) ? Number::upward : Number::getround());
+
7585 auto const res = root2(amount * amount2);
+
7586
+
7587 if (shouldFail)
+
7588 BEAST_EXPECT(res < lptBalance);
+
7589 else
+
7590 BEAST_EXPECT(res >= lptBalance);
+
7591 }
+
7592
+
7593 void
+
7594 testDepositRounding(FeatureBitset all)
+
7595 {
+
7596 testcase("Deposit Rounding");
+
7597 using namespace jtx;
+
7598
+
7599 // Single asset deposit
+
7600 for (auto const& deposit :
+
7601 {STAmount(EUR, 1, 1),
+
7602 STAmount(EUR, 1, 2),
+
7603 STAmount(EUR, 1, 5),
+
7604 STAmount(EUR, 1, -3), // fail
+
7605 STAmount(EUR, 1, -6),
+
7606 STAmount(EUR, 1, -9)})
+
7607 {
+
7608 testAMM(
+
7609 [&](AMM& ammAlice, Env& env) {
+
7610 fund(
+
7611 env,
+
7612 gw,
+
7613 {bob},
+
7614 XRP(10'000'000),
+
7615 {GBP(100'000), EUR(100'000)},
+
7616 Fund::Acct);
+
7617 env.close();
+
7618
+
7619 ammAlice.deposit(
+
7620 DepositArg{.account = bob, .asset1In = deposit});
+
7621 invariant(
+
7622 ammAlice,
+
7623 env,
+
7624 "dep1",
+
7625 deposit == STAmount{EUR, 1, -3} &&
+
7626 !env.enabled(fixAMMv1_3));
+
7627 },
+
7628 {{GBP(30'000), EUR(30'000)}},
+
7629 0,
+
7630 std::nullopt,
+
7631 {all});
+
7632 }
+
7633
+
7634 // Two-asset proportional deposit (1:1 pool ratio)
+
7635 testAMM(
+
7636 [&](AMM& ammAlice, Env& env) {
+
7637 fund(
+
7638 env,
+
7639 gw,
+
7640 {bob},
+
7641 XRP(10'000'000),
+
7642 {GBP(100'000), EUR(100'000)},
+
7643 Fund::Acct);
+
7644 env.close();
+
7645
+
7646 STAmount const depositEuro{
+
7647 EUR, UINT64_C(10'1234567890123456), -16};
+
7648 STAmount const depositGBP{
+
7649 GBP, UINT64_C(10'1234567890123456), -16};
+
7650
+
7651 ammAlice.deposit(DepositArg{
+
7652 .account = bob,
+
7653 .asset1In = depositEuro,
+
7654 .asset2In = depositGBP});
+
7655 invariant(ammAlice, env, "dep2", false);
+
7656 },
+
7657 {{GBP(30'000), EUR(30'000)}},
+
7658 0,
+
7659 std::nullopt,
+
7660 {all});
+
7661
+
7662 // Two-asset proportional deposit (1:3 pool ratio)
+
7663 for (auto const& exponent : {1, 2, 3, 4, -3 /*fail*/, -6, -9})
+
7664 {
+
7665 testAMM(
+
7666 [&](AMM& ammAlice, Env& env) {
+
7667 fund(
+
7668 env,
+
7669 gw,
+
7670 {bob},
+
7671 XRP(10'000'000),
+
7672 {GBP(100'000), EUR(100'000)},
+
7673 Fund::Acct);
+
7674 env.close();
+
7675
+
7676 STAmount const depositEuro{EUR, 1, exponent};
+
7677 STAmount const depositGBP{GBP, 1, exponent};
+
7678
+
7679 ammAlice.deposit(DepositArg{
+
7680 .account = bob,
+
7681 .asset1In = depositEuro,
+
7682 .asset2In = depositGBP});
+
7683 invariant(
+
7684 ammAlice,
+
7685 env,
+
7686 "dep3",
+
7687 exponent != -3 && !env.enabled(fixAMMv1_3));
+
7688 },
+
7689 {{GBP(10'000), EUR(30'000)}},
+
7690 0,
+
7691 std::nullopt,
+
7692 {all});
+
7693 }
+
7694
+
7695 // tfLPToken deposit
+
7696 testAMM(
+
7697 [&](AMM& ammAlice, Env& env) {
+
7698 fund(
+
7699 env,
+
7700 gw,
+
7701 {bob},
+
7702 XRP(10'000'000),
+
7703 {GBP(100'000), EUR(100'000)},
+
7704 Fund::Acct);
+
7705 env.close();
+
7706
+
7707 ammAlice.deposit(DepositArg{
+
7708 .account = bob,
+
7709 .tokens = IOUAmount{10'1234567890123456, -16}});
+
7710 invariant(ammAlice, env, "dep4", false);
+
7711 },
+
7712 {{GBP(7'000), EUR(30'000)}},
+
7713 0,
+
7714 std::nullopt,
+
7715 {all});
+
7716
+
7717 // tfOneAssetLPToken deposit
+
7718 for (auto const& tokens :
+
7719 {IOUAmount{1, -3},
+
7720 IOUAmount{1, -2},
+
7721 IOUAmount{1, -1},
+
7722 IOUAmount{1},
+
7723 IOUAmount{10},
+
7724 IOUAmount{100},
+
7725 IOUAmount{1'000},
+
7726 IOUAmount{10'000}})
+
7727 {
+
7728 testAMM(
+
7729 [&](AMM& ammAlice, Env& env) {
+
7730 fund(
+
7731 env,
+
7732 gw,
+
7733 {bob},
+
7734 XRP(10'000'000),
+
7735 {GBP(100'000), EUR(1'000'000)},
+
7736 Fund::Acct);
+
7737 env.close();
+
7738
+
7739 ammAlice.deposit(DepositArg{
+
7740 .account = bob,
+
7741 .tokens = tokens,
+
7742 .asset1In = STAmount{EUR, 1, 6}});
+
7743 invariant(ammAlice, env, "dep5", false);
+
7744 },
+
7745 {{GBP(7'000), EUR(30'000)}},
+
7746 0,
+
7747 std::nullopt,
+
7748 {all});
+
7749 }
+
7750
+
7751 // Single deposit with EP not exceeding specified:
+
7752 // 1'000 GBP with EP not to exceed 5 (GBP/TokensOut)
+
7753 testAMM(
+
7754 [&](AMM& ammAlice, Env& env) {
+
7755 fund(
+
7756 env,
+
7757 gw,
+
7758 {bob},
+
7759 XRP(10'000'000),
+
7760 {GBP(100'000), EUR(100'000)},
+
7761 Fund::Acct);
+
7762 env.close();
+
7763
+
7764 ammAlice.deposit(
+
7765 bob, GBP(1'000), std::nullopt, STAmount{GBP, 5});
+
7766 invariant(ammAlice, env, "dep6", false);
+
7767 },
+
7768 {{GBP(30'000), EUR(30'000)}},
+
7769 0,
+
7770 std::nullopt,
+
7771 {all});
+
7772 }
+
7773
+
7774 void
+
7775 testWithdrawRounding(FeatureBitset all)
+
7776 {
+
7777 testcase("Withdraw Rounding");
+
7778
+
7779 using namespace jtx;
+
7780
+
7781 // tfLPToken mode
+
7782 testAMM(
+
7783 [&](AMM& ammAlice, Env& env) {
+
7784 ammAlice.withdraw(alice, 1'000);
+
7785 invariant(ammAlice, env, "with1", false);
+
7786 },
+
7787 {{GBP(7'000), EUR(30'000)}},
+
7788 0,
+
7789 std::nullopt,
+
7790 {all});
+
7791
+
7792 // tfWithdrawAll mode
+
7793 testAMM(
+
7794 [&](AMM& ammAlice, Env& env) {
+
7795 ammAlice.withdraw(
+
7796 WithdrawArg{.account = alice, .flags = tfWithdrawAll});
+
7797 invariant(ammAlice, env, "with2", false);
+
7798 },
+
7799 {{GBP(7'000), EUR(30'000)}},
+
7800 0,
+
7801 std::nullopt,
+
7802 {all});
+
7803
+
7804 // tfTwoAsset withdraw mode
+
7805 testAMM(
+
7806 [&](AMM& ammAlice, Env& env) {
+
7807 ammAlice.withdraw(WithdrawArg{
+
7808 .account = alice,
+
7809 .asset1Out = STAmount{GBP, 3'500},
+
7810 .asset2Out = STAmount{EUR, 15'000},
+
7811 .flags = tfTwoAsset});
+
7812 invariant(ammAlice, env, "with3", false);
+
7813 },
+
7814 {{GBP(7'000), EUR(30'000)}},
+
7815 0,
+
7816 std::nullopt,
+
7817 {all});
+
7818
+
7819 // tfSingleAsset withdraw mode
+
7820 // Note: This test fails with 0 trading fees, but doesn't fail if
+
7821 // trading fees is set to 1'000 -- I suspect the compound operations
+
7822 // in AMMHelpers.cpp:withdrawByTokens compensate for the rounding
+
7823 // errors
+
7824 testAMM(
+
7825 [&](AMM& ammAlice, Env& env) {
+
7826 ammAlice.withdraw(WithdrawArg{
+
7827 .account = alice,
+
7828 .asset1Out = STAmount{GBP, 1'234},
+
7829 .flags = tfSingleAsset});
+
7830 invariant(ammAlice, env, "with4", false);
+
7831 },
+
7832 {{GBP(7'000), EUR(30'000)}},
+
7833 0,
+
7834 std::nullopt,
+
7835 {all});
+
7836
+
7837 // tfOneAssetWithdrawAll mode
+
7838 testAMM(
+
7839 [&](AMM& ammAlice, Env& env) {
+
7840 fund(
+
7841 env,
+
7842 gw,
+
7843 {bob},
+
7844 XRP(10'000'000),
+
7845 {GBP(100'000), EUR(100'000)},
+
7846 Fund::Acct);
+
7847 env.close();
+
7848
+
7849 ammAlice.deposit(DepositArg{
+
7850 .account = bob, .asset1In = STAmount{GBP, 3'456}});
+
7851
+
7852 ammAlice.withdraw(WithdrawArg{
+
7853 .account = bob,
+
7854 .asset1Out = STAmount{GBP, 1'000},
+
7855 .flags = tfOneAssetWithdrawAll});
+
7856 invariant(ammAlice, env, "with5", false);
+
7857 },
+
7858 {{GBP(7'000), EUR(30'000)}},
+
7859 0,
+
7860 std::nullopt,
+
7861 {all});
+
7862
+
7863 // tfOneAssetLPToken mode
+
7864 testAMM(
+
7865 [&](AMM& ammAlice, Env& env) {
+
7866 ammAlice.withdraw(WithdrawArg{
+
7867 .account = alice,
+
7868 .tokens = 1'000,
+
7869 .asset1Out = STAmount{GBP, 100},
+
7870 .flags = tfOneAssetLPToken});
+
7871 invariant(ammAlice, env, "with6", false);
+
7872 },
+
7873 {{GBP(7'000), EUR(30'000)}},
+
7874 0,
+
7875 std::nullopt,
+
7876 {all});
+
7877
+
7878 // tfLimitLPToken mode
+
7879 testAMM(
+
7880 [&](AMM& ammAlice, Env& env) {
+
7881 ammAlice.withdraw(WithdrawArg{
+
7882 .account = alice,
+
7883 .asset1Out = STAmount{GBP, 100},
+
7884 .maxEP = IOUAmount{2},
+
7885 .flags = tfLimitLPToken});
+
7886 invariant(ammAlice, env, "with7", true);
+
7887 },
+
7888 {{GBP(7'000), EUR(30'000)}},
+
7889 0,
+
7890 std::nullopt,
+
7891 {all});
+
7892 }
+
7893
+
7894 void
+
7895 run() override
+
7896 {
+
7897 FeatureBitset const all{jtx::supported_amendments()};
+
7898 testInvalidInstance();
+
7899 testInstanceCreate();
+
7900 testInvalidDeposit(all);
+
7901 testInvalidDeposit(all - featureAMMClawback);
+
7902 testDeposit();
+
7903 testInvalidWithdraw();
+
7904 testWithdraw();
+
7905 testInvalidFeeVote();
+
7906 testFeeVote();
+
7907 testInvalidBid();
+
7908 testBid(all);
+
7909 testBid(all - fixAMMv1_3);
+
7910 testBid(all - fixAMMv1_1 - fixAMMv1_3);
+
7911 testInvalidAMMPayment();
+
7912 testBasicPaymentEngine(all);
+
7913 testBasicPaymentEngine(all - fixAMMv1_1 - fixAMMv1_3);
+
7914 testBasicPaymentEngine(all - fixReducedOffersV2);
+
7915 testBasicPaymentEngine(
+
7916 all - fixAMMv1_1 - fixAMMv1_3 - fixReducedOffersV2);
+
7917 testAMMTokens();
+
7918 testAmendment();
+
7919 testFlags();
+
7920 testRippling();
+
7921 testAMMAndCLOB(all);
+
7922 testAMMAndCLOB(all - fixAMMv1_1 - fixAMMv1_3);
+
7923 testTradingFee(all);
+
7924 testTradingFee(all - fixAMMv1_3);
+
7925 testTradingFee(all - fixAMMv1_1 - fixAMMv1_3);
+
7926 testAdjustedTokens(all);
+
7927 testAdjustedTokens(all - fixAMMv1_3);
+
7928 testAdjustedTokens(all - fixAMMv1_1 - fixAMMv1_3);
+
7929 testAutoDelete();
+
7930 testClawback();
+
7931 testAMMID();
+
7932 testSelection(all);
+
7933 testSelection(all - fixAMMv1_1 - fixAMMv1_3);
+
7934 testFixDefaultInnerObj();
+
7935 testMalformed();
+
7936 testFixOverflowOffer(all);
+
7937 testFixOverflowOffer(all - fixAMMv1_3);
+
7938 testFixOverflowOffer(all - fixAMMv1_1 - fixAMMv1_3);
+
7939 testSwapRounding();
+
7940 testFixChangeSpotPriceQuality(all);
+
7941 testFixChangeSpotPriceQuality(all - fixAMMv1_1 - fixAMMv1_3);
+
7942 testFixAMMOfferBlockedByLOB(all);
+
7943 testFixAMMOfferBlockedByLOB(all - fixAMMv1_1 - fixAMMv1_3);
+
7944 testLPTokenBalance(all);
+
7945 testLPTokenBalance(all - fixAMMv1_3);
+
7946 testLPTokenBalance(all - fixAMMv1_1 - fixAMMv1_3);
+
7947 testAMMClawback(all);
+
7948 testAMMClawback(all - featureAMMClawback);
+
7949 testAMMClawback(all - fixAMMv1_1 - fixAMMv1_3 - featureAMMClawback);
+
7950 testAMMDepositWithFrozenAssets(all);
+
7951 testAMMDepositWithFrozenAssets(all - featureAMMClawback);
+
7952 testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
+
7953 testAMMDepositWithFrozenAssets(
+
7954 all - fixAMMv1_1 - fixAMMv1_3 - featureAMMClawback);
+
7955 testFixReserveCheckOnWithdrawal(all);
+
7956 testFixReserveCheckOnWithdrawal(all - fixAMMv1_2);
+
7957 testDepositAndWithdrawRounding(all);
+
7958 testDepositAndWithdrawRounding(all - fixAMMv1_3);
+
7959 testDepositRounding(all);
+
7960 testDepositRounding(all - fixAMMv1_3);
+
7961 testWithdrawRounding(all);
+
7962 testWithdrawRounding(all - fixAMMv1_3);
+
7963 testFailedPseudoAccount();
+
7964 }
+
7965};
+
7966
+
7967BEAST_DEFINE_TESTSUITE_PRIO(AMM, app, ripple, 1);
+
7968
+
7969} // namespace test
+
7970} // namespace ripple
Represents a JSON value.
Definition: json_value.h:150
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:482
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
-
RAII class to set and restore the current transaction rules.
Definition: Rules.h:108
+
RAII class to set and restore the current transaction rules.
Definition: Rules.h:111
Definition: Feature.h:147
Floating point representation of amounts with high dynamic range.
Definition: IOUAmount.h:46
-
std::int64_t mantissa() const noexcept
Definition: IOUAmount.h:172
+
std::int64_t mantissa() const noexcept
Definition: IOUAmount.h:178
A currency issued by an account.
Definition: Issue.h:36
Currency currency
Definition: Issue.h:38
+
Definition: Number.h:393
Definition: Number.h:36
constexpr int exponent() const noexcept
Definition: Number.h:218
constexpr rep mantissa() const noexcept
Definition: Number.h:212
@@ -7357,36 +8064,36 @@ $(function() {
Definition: TER.h:409
Definition: XRPAmount.h:43
-
jtx::Account const alice
Definition: AMMTest.h:68
-
jtx::IOU const BTC
Definition: AMMTest.h:73
-
jtx::IOU const USD
Definition: AMMTest.h:70
-
jtx::Account const gw
Definition: AMMTest.h:66
-
jtx::Account const bob
Definition: AMMTest.h:69
-
jtx::IOU const EUR
Definition: AMMTest.h:71
-
jtx::IOU const GBP
Definition: AMMTest.h:72
-
jtx::Account const carol
Definition: AMMTest.h:67
-
jtx::IOU const BAD
Definition: AMMTest.h:74
-
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> &&cb, std::optional< std::pair< STAmount, STAmount > > const &pool=std::nullopt, std::uint16_t tfee=0, std::optional< jtx::ter > const &ter=std::nullopt, std::vector< FeatureBitset > const &features={supported_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
Definition: AMMTest.cpp:102
-
Definition: AMMTest.h:93
-
XRPAmount ammCrtFee(jtx::Env &env) const
Definition: AMMTest.cpp:160
-
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Definition: AMMTest.cpp:154
+
jtx::Account const alice
Definition: AMMTest.h:77
+
jtx::IOU const BTC
Definition: AMMTest.h:82
+
jtx::IOU const USD
Definition: AMMTest.h:79
+
jtx::Account const gw
Definition: AMMTest.h:75
+
jtx::Account const bob
Definition: AMMTest.h:78
+
jtx::IOU const EUR
Definition: AMMTest.h:80
+
jtx::IOU const GBP
Definition: AMMTest.h:81
+
jtx::Account const carol
Definition: AMMTest.h:76
+
jtx::IOU const BAD
Definition: AMMTest.h:83
+
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> &&cb, std::optional< std::pair< STAmount, STAmount > > const &pool=std::nullopt, std::uint16_t tfee=0, std::optional< jtx::ter > const &ter=std::nullopt, std::vector< FeatureBitset > const &features={supported_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
Definition: AMMTest.cpp:103
+
Definition: AMMTest.h:107
+
XRPAmount ammCrtFee(jtx::Env &env) const
Definition: AMMTest.cpp:177
+
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Definition: AMMTest.cpp:171
Convenience class to test AMM functionality.
Definition: AMM.h:124
-
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
Definition: AMM.cpp:161
-
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:637
-
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
Definition: AMM.cpp:275
-
IOUAmount tokens() const
Definition: AMM.h:337
-
AccountID const & ammAccount() const
Definition: AMM.h:325
-
bool expectTradingFee(std::uint16_t fee) const
Definition: AMM.cpp:312
-
IOUAmount withdraw(std::optional< Account > const &account, std::optional< LPToken > const &tokens, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:537
-
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition: AMM.cpp:232
-
Issue lptIssue() const
Definition: AMM.h:331
-
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:411
-
IOUAmount getLPTokensBalance(std::optional< AccountID > const &account=std::nullopt) const
Definition: AMM.cpp:245
-
Json::Value bid(BidArg const &arg)
Definition: AMM.cpp:664
-
void setTokens(Json::Value &jv, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt)
Definition: AMM.cpp:371
-
bool ammExists() const
Definition: AMM.cpp:320
-
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.h:273
-
bool expectLPTokens(AccountID const &account, IOUAmount const &tokens) const
Definition: AMM.cpp:262
+
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
Definition: AMM.cpp:166
+
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:642
+
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
Definition: AMM.cpp:280
+
IOUAmount tokens() const
Definition: AMM.h:343
+
AccountID const & ammAccount() const
Definition: AMM.h:331
+
bool expectTradingFee(std::uint16_t fee) const
Definition: AMM.cpp:317
+
IOUAmount withdraw(std::optional< Account > const &account, std::optional< LPToken > const &tokens, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:542
+
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition: AMM.cpp:237
+
Issue lptIssue() const
Definition: AMM.h:337
+
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:416
+
IOUAmount getLPTokensBalance(std::optional< AccountID > const &account=std::nullopt) const
Definition: AMM.cpp:250
+
Json::Value bid(BidArg const &arg)
Definition: AMM.cpp:669
+
void setTokens(Json::Value &jv, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt)
Definition: AMM.cpp:376
+
bool ammExists() const
Definition: AMM.cpp:325
+
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.h:279
+
bool expectLPTokens(AccountID const &account, IOUAmount const &tokens) const
Definition: AMM.cpp:267
Immutable cryptographic account descriptor.
Definition: Account.h:39
AccountID id() const
Returns the Account ID.
Definition: Account.h:107
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
@@ -7394,11 +8101,12 @@ $(function() {
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition: Env.cpp:111
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:331
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
+
bool enabled(uint256 feature) const
Definition: Env.h:626
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:264
Account const & master
Definition: Env.h:125
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:284
beast::Journal const journal
Definition: Env.h:162
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition: Env.cpp:447
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition: Env.cpp:179
@@ -7420,7 +8128,7 @@ $(function() {
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:46
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:446
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:374
-
Json::Value pay(Account const &account, AccountID const &to, STAmount const &amount)
Definition: AMM.cpp:817
+
Json::Value pay(Account const &account, AccountID const &to, STAmount const &amount)
Definition: AMM.cpp:822
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition: flags.h:41
Json::Value claw(Account const &account, STAmount const &amount, std::optional< Account > const &mptHolder)
Definition: trust.cpp:69
bool expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
@@ -7430,7 +8138,7 @@ $(function() {
bool expectLine(Env &env, AccountID const &account, STAmount const &value, bool defaultLimits)
Json::Value getAccountLines(Env &env, AccountID const &acctId)
Definition: TestHelpers.cpp:40
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
-
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition: AMMTest.cpp:36
+
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition: AMMTest.cpp:37
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
Json::Value accountBalance(Env &env, Account const &acct)
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition: rate.cpp:32
@@ -7450,11 +8158,11 @@ $(function() {
bool isXRP(AccountID const &c)
Definition: AccountID.h:91
@ AMM
std::uint16_t constexpr AUCTION_SLOT_TIME_INTERVALS
Definition: AMMCore.h:35
-
std::optional< Number > solveQuadraticEqSmallest(Number const &a, Number const &b, Number const &c)
Solve quadratic equation to find takerGets or takerPays.
Definition: AMMHelpers.cpp:227
+
std::optional< Number > solveQuadraticEqSmallest(Number const &a, Number const &b, Number const &c)
Solve quadratic equation to find takerGets or takerPays.
Definition: AMMHelpers.cpp:271
@ telINSUF_FEE_P
Definition: TER.h:57
@ Fail
Should not be retried in this ledger.
Issue getIssue(T const &amt)
-
TOut swapAssetIn(TAmounts< TIn, TOut > const &pool, TIn const &assetIn, std::uint16_t tfee)
AMM pool invariant - the product (A * B) after swap in/out has to remain at least the same: (A + in) ...
Definition: AMMHelpers.h:462
+
TOut swapAssetIn(TAmounts< TIn, TOut > const &pool, TIn const &assetIn, std::uint16_t tfee)
AMM pool invariant - the product (A * B) after swap in/out has to remain at least the same: (A + in) ...
Definition: AMMHelpers.h:464
@ lsfDefaultRipple
@ lsfDisableMaster
@ lsfDepositAuth
@@ -7469,10 +8177,11 @@ $(function() {
@ all
constexpr std::uint32_t tfWithdrawAll
Definition: TxFlags.h:215
base_uint< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition: UintTypes.h:56
-
std::optional< TAmounts< TIn, TOut > > changeSpotPriceQuality(TAmounts< TIn, TOut > const &pool, Quality const &quality, std::uint16_t tfee, Rules const &rules, beast::Journal j)
Generate AMM offer so that either updated Spot Price Quality (SPQ) is equal to LOB quality (in this c...
Definition: AMMHelpers.h:329
+
std::optional< TAmounts< TIn, TOut > > changeSpotPriceQuality(TAmounts< TIn, TOut > const &pool, Quality const &quality, std::uint16_t tfee, Rules const &rules, beast::Journal j)
Generate AMM offer so that either updated Spot Price Quality (SPQ) is equal to LOB quality (in this c...
Definition: AMMHelpers.h:331
constexpr std::uint32_t tfSetfAuth
Definition: TxFlags.h:114
constexpr std::uint32_t asfDefaultRipple
Definition: TxFlags.h:84
constexpr std::uint32_t tfClearFreeze
Definition: TxFlags.h:118
+
STAmount ammAssetOut(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
Definition: AMMHelpers.cpp:145
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition: Issue.h:126
@ tecINSUF_RESERVE_LINE
Definition: TER.h:288
@ tecINCOMPLETE
Definition: TER.h:335
@@ -7481,6 +8190,7 @@ $(function() {
@ tecOWNERS
Definition: TER.h:298
@ tecDUPLICATE
Definition: TER.h:315
@ tecNO_PERMISSION
Definition: TER.h:305
+
@ tecINVARIANT_FAILED
Definition: TER.h:313
@ tecAMM_NOT_EMPTY
Definition: TER.h:333
@ tecPATH_PARTIAL
Definition: TER.h:282
@ tecUNFUNDED_AMM
Definition: TER.h:328
@@ -7507,8 +8217,7 @@ $(function() {
@ terNO_RIPPLE
Definition: TER.h:224
@ terNO_AMM
Definition: TER.h:227
constexpr std::uint32_t tfSetFreeze
Definition: TxFlags.h:117
-
STAmount withdrawByTokens(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
Definition: AMMHelpers.cpp:114
-
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition: AMMHelpers.h:127
+
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition: AMMHelpers.h:129
Number root2(Number f)
Definition: Number.cpp:701
@ temBAD_AMOUNT
Definition: TER.h:89
@ temBAD_FEE
Definition: TER.h:92
@@ -7517,7 +8226,7 @@ $(function() {
@ temBAD_AMM_TOKENS
Definition: TER.h:129
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
-
TIn swapAssetOut(TAmounts< TIn, TOut > const &pool, TOut const &assetOut, std::uint16_t tfee)
Swap assetOut out of the pool and swap in a proportional amount of the other asset.
Definition: AMMHelpers.h:535
+
TIn swapAssetOut(TAmounts< TIn, TOut > const &pool, TOut const &assetOut, std::uint16_t tfee)
Swap assetOut out of the pool and swap in a proportional amount of the other asset.
Definition: AMMHelpers.h:537
@@ -7526,45 +8235,53 @@ $(function() {
Zero allows classes to offer efficient comparisons to zero.
Definition: Zero.h:43
@ none
Definition: STBase.h:44
uint256 key
Definition: Keylet.h:40
-
Basic tests of AMM that do not use offers.
Definition: AMM_test.cpp:50
-
void testBid(FeatureBitset features)
Definition: AMM_test.cpp:2857
-
void testRippling()
Definition: AMM_test.cpp:4660
-
void testAMMTokens()
Definition: AMM_test.cpp:4500
-
void testInvalidDeposit(FeatureBitset features)
Definition: AMM_test.cpp:437
-
void testAMMID()
Definition: AMM_test.cpp:5460
-
void testFeeVote()
Definition: AMM_test.cpp:2527
-
void testWithdraw()
Definition: AMM_test.cpp:2140
-
void testInvalidAMMPayment()
Definition: AMM_test.cpp:3368
-
void testSelection(FeatureBitset features)
Definition: AMM_test.cpp:5513
-
void testAMMClawback(FeatureBitset features)
Definition: AMM_test.cpp:6981
-
void testInvalidFeeVote()
Definition: AMM_test.cpp:2458
-
void testSwapRounding()
Definition: AMM_test.cpp:6698
-
void testLPTokenBalance(FeatureBitset features)
Definition: AMM_test.cpp:6863
-
void testDeposit()
Definition: AMM_test.cpp:1322
-
void run() override
Runs the suite.
Definition: AMM_test.cpp:7203
-
void testInstanceCreate()
Definition: AMM_test.cpp:53
-
void testTradingFee(FeatureBitset features)
Definition: AMM_test.cpp:4781
-
void testFixOverflowOffer(FeatureBitset features)
Definition: AMM_test.cpp:6373
-
void testInvalidWithdraw()
Definition: AMM_test.cpp:1622
-
void testAMMAndCLOB(FeatureBitset features)
Definition: AMM_test.cpp:4710
-
void testInvalidInstance()
Definition: AMM_test.cpp:154
-
void testMalformed()
Definition: AMM_test.cpp:6314
-
void testAutoDelete()
Definition: AMM_test.cpp:5335
-
void testFailedPseudoAccount()
Definition: AMM_test.cpp:7159
-
void testBasicPaymentEngine(FeatureBitset features)
Definition: AMM_test.cpp:3531
-
void testInvalidBid()
Definition: AMM_test.cpp:2632
-
void testFixChangeSpotPriceQuality(FeatureBitset features)
Definition: AMM_test.cpp:6096
-
void testAmendment()
Definition: AMM_test.cpp:4611
-
void testClawback()
Definition: AMM_test.cpp:5448
-
void testFixDefaultInnerObj()
Definition: AMM_test.cpp:5996
-
void testFixReserveCheckOnWithdrawal(FeatureBitset features)
Definition: AMM_test.cpp:7110
-
void testAdjustedTokens(FeatureBitset features)
Definition: AMM_test.cpp:5170
-
void testFlags()
Definition: AMM_test.cpp:4639
-
void testAMMDepositWithFrozenAssets(FeatureBitset features)
Definition: AMM_test.cpp:7028
-
void testFixAMMOfferBlockedByLOB(FeatureBitset features)
Definition: AMM_test.cpp:6735
+
Basic tests of AMM that do not use offers.
Definition: AMM_test.cpp:51
+
void testBid(FeatureBitset features)
Definition: AMM_test.cpp:3029
+
void testRippling()
Definition: AMM_test.cpp:4903
+
void testFixOverflowOffer(FeatureBitset featuresInitial)
Definition: AMM_test.cpp:6682
+
void testAMMTokens()
Definition: AMM_test.cpp:4743
+
void testInvalidDeposit(FeatureBitset features)
Definition: AMM_test.cpp:438
+
void testDepositRounding(FeatureBitset all)
Definition: AMM_test.cpp:7594
+
void testAMMID()
Definition: AMM_test.cpp:5769
+
void testFeeVote()
Definition: AMM_test.cpp:2687
+
void testWithdraw()
Definition: AMM_test.cpp:2265
+
void testInvalidAMMPayment()
Definition: AMM_test.cpp:3611
+
void testSelection(FeatureBitset features)
Definition: AMM_test.cpp:5822
+
void testAMMClawback(FeatureBitset features)
Definition: AMM_test.cpp:7300
+
void testInvalidFeeVote()
Definition: AMM_test.cpp:2618
+
void testSwapRounding()
Definition: AMM_test.cpp:7013
+
void testLPTokenBalance(FeatureBitset features)
Definition: AMM_test.cpp:7178
+
void testDeposit()
Definition: AMM_test.cpp:1383
+
void run() override
Runs the suite.
Definition: AMM_test.cpp:7895
+
void testInstanceCreate()
Definition: AMM_test.cpp:54
+
void testTradingFee(FeatureBitset features)
Definition: AMM_test.cpp:5024
+
void testInvalidWithdraw()
Definition: AMM_test.cpp:1685
+
void testAMMAndCLOB(FeatureBitset features)
Definition: AMM_test.cpp:4953
+
void testInvalidInstance()
Definition: AMM_test.cpp:155
+
void testMalformed()
Definition: AMM_test.cpp:6623
+
void testDepositAndWithdrawRounding(FeatureBitset features)
Definition: AMM_test.cpp:7523
+
void testAutoDelete()
Definition: AMM_test.cpp:5644
+
void testFailedPseudoAccount()
Definition: AMM_test.cpp:7478
+
void invariant(jtx::AMM &amm, jtx::Env &env, std::string const &msg, bool shouldFail)
Definition: AMM_test.cpp:7575
+
void testBasicPaymentEngine(FeatureBitset features)
Definition: AMM_test.cpp:3774
+
void testInvalidBid()
Definition: AMM_test.cpp:2804
+
void testWithdrawRounding(FeatureBitset all)
Definition: AMM_test.cpp:7775
+
void testFixChangeSpotPriceQuality(FeatureBitset features)
Definition: AMM_test.cpp:6405
+
void testAmendment()
Definition: AMM_test.cpp:4854
+
void testClawback()
Definition: AMM_test.cpp:5757
+
void testFixDefaultInnerObj()
Definition: AMM_test.cpp:6305
+
void testFixReserveCheckOnWithdrawal(FeatureBitset features)
Definition: AMM_test.cpp:7429
+
void testAdjustedTokens(FeatureBitset features)
Definition: AMM_test.cpp:5423
+
void testFlags()
Definition: AMM_test.cpp:4882
+
void testAMMDepositWithFrozenAssets(FeatureBitset features)
Definition: AMM_test.cpp:7347
+
void testFixAMMOfferBlockedByLOB(FeatureBitset features)
Definition: AMM_test.cpp:7050
+
Definition: AMM.h:112
+
Definition: AMM.h:63
+
std::uint16_t tfee
Definition: AMM.h:65
Definition: AMM.h:75
std::optional< Account > account
Definition: AMM.h:76
std::optional< STAmount > asset1In
Definition: AMM.h:78
+
std::optional< LPToken > tokens
Definition: AMM.h:77
Definition: AMM.h:102
std::optional< Account > account
Definition: AMM.h:103
std::uint32_t tfee
Definition: AMM.h:104
diff --git a/AccountDelete__test_8cpp_source.html b/AccountDelete__test_8cpp_source.html index bf29b46f33..1d019eb02a 100644 --- a/AccountDelete__test_8cpp_source.html +++ b/AccountDelete__test_8cpp_source.html @@ -1380,7 +1380,7 @@ $(function() {
A transaction testing environment.
Definition: Env.h:121
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:460
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
A balance matches.
Definition: balance.h:39
Definition: credentials.h:63
diff --git a/AccountInfo__test_8cpp_source.html b/AccountInfo__test_8cpp_source.html index 37c704cd3b..195b6c6f7e 100644 --- a/AccountInfo__test_8cpp_source.html +++ b/AccountInfo__test_8cpp_source.html @@ -789,7 +789,7 @@ $(function() {
void testErrors()
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:121
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: rpc.h:35
T data(T... args)
T emplace(T... args)
diff --git a/AccountLines__test_8cpp_source.html b/AccountLines__test_8cpp_source.html index d2c4392b6c..4424ed4721 100644 --- a/AccountLines__test_8cpp_source.html +++ b/AccountLines__test_8cpp_source.html @@ -1682,8 +1682,8 @@ $(function() {
Keylet signers(AccountID const &account) noexcept
A SignerList.
Definition: Indexes.cpp:330
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:274
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition: Indexes.cpp:395
-
Json::Value trust(AccountID const &account, STAmount const &amount, std::uint32_t flags=0)
Definition: AMM.cpp:805
-
Json::Value pay(Account const &account, AccountID const &to, STAmount const &amount)
Definition: AMM.cpp:817
+
Json::Value trust(AccountID const &account, STAmount const &amount, std::uint32_t flags=0)
Definition: AMM.cpp:810
+
Json::Value pay(Account const &account, AccountID const &to, STAmount const &amount)
Definition: AMM.cpp:822
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value finish(AccountID const &account, AccountID const &from, std::uint32_t seq)
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
diff --git a/AccountObjects__test_8cpp_source.html b/AccountObjects__test_8cpp_source.html index 3e778996b9..4d056cb74e 100644 --- a/AccountObjects__test_8cpp_source.html +++ b/AccountObjects__test_8cpp_source.html @@ -1561,7 +1561,7 @@ $(function() {
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:264
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:284
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
Set the fee on a JTx.
Definition: fee.h:37
Match set account flags.
Definition: flags.h:125
diff --git a/AccountOffers__test_8cpp_source.html b/AccountOffers__test_8cpp_source.html index 95c537c046..42d6a68a99 100644 --- a/AccountOffers__test_8cpp_source.html +++ b/AccountOffers__test_8cpp_source.html @@ -456,7 +456,7 @@ $(function() {
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
A transaction testing environment.
Definition: Env.h:121
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:264
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: rpc.h:35
@ nullValue
'null' value
Definition: json_value.h:39
diff --git a/AccountTxPaging__test_8cpp_source.html b/AccountTxPaging__test_8cpp_source.html index 142b52ede1..a6b310bc43 100644 --- a/AccountTxPaging__test_8cpp_source.html +++ b/AccountTxPaging__test_8cpp_source.html @@ -361,7 +361,7 @@ $(function() {
void testAccountTxPaging()
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:121
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
@ nullValue
'null' value
Definition: json_value.h:39
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
diff --git a/AccountTx__test_8cpp_source.html b/AccountTx__test_8cpp_source.html index 6cbe2f3376..7db7071658 100644 --- a/AccountTx__test_8cpp_source.html +++ b/AccountTx__test_8cpp_source.html @@ -867,7 +867,7 @@ $(function() {
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:331
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:284
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition: Env.cpp:179
Set the fee on a JTx.
Definition: fee.h:37
diff --git a/AmendmentTable_8cpp_source.html b/AmendmentTable_8cpp_source.html index fa5afd462a..6ffd45e0da 100644 --- a/AmendmentTable_8cpp_source.html +++ b/AmendmentTable_8cpp_source.html @@ -1172,7 +1172,7 @@ $(function() {
Definition: Application.h:114
Definition: DatabaseCon.h:86
LockedSociSession checkoutDb()
Definition: DatabaseCon.h:190
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
Holds a collection of configuration values.
Definition: BasicConfig.h:45
std::string const & name() const
Returns the name of this section.
Definition: BasicConfig.h:61
diff --git a/AmendmentTable_8h_source.html b/AmendmentTable_8h_source.html index 29f4ccd007..31bbe9638e 100644 --- a/AmendmentTable_8h_source.html +++ b/AmendmentTable_8h_source.html @@ -286,7 +286,7 @@ $(function() {
virtual bool isEnabled(uint256 const &amendment) const =0
virtual bool needValidatedLedger(LedgerIndex seq) const =0
Called to determine whether the amendment logic needs to process a new validated ledger.
virtual ~AmendmentTable()=default
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
void add(Serializer &s) const override
Definition: STObject.cpp:141
Definition: STTx.h:48
uint256 getTransactionID() const
Definition: STTx.h:219
diff --git a/AmendmentTable__test_8cpp_source.html b/AmendmentTable__test_8cpp_source.html index 05ec952b09..e1f2c18ba8 100644 --- a/AmendmentTable__test_8cpp_source.html +++ b/AmendmentTable__test_8cpp_source.html @@ -1442,7 +1442,7 @@ $(function() {
virtual std::map< uint256, std::uint32_t > doVoting(Rules const &rules, NetClock::time_point closeTime, std::set< uint256 > const &enabledAmendments, majorityAmendments_t const &majorityAmendments, std::vector< std::shared_ptr< STValidation > > const &valSet)=0
Definition: Application.h:114
Definition: Feature.h:147
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
Definition: STValidation.h:47
Definition: STVector256.h:31
diff --git a/AmountConversions_8h_source.html b/AmountConversions_8h_source.html index c5c0e8f0a7..91a4711a2d 100644 --- a/AmountConversions_8h_source.html +++ b/AmountConversions_8h_source.html @@ -297,9 +297,9 @@ $(function() {
219
220#endif
Floating point representation of amounts with high dynamic range.
Definition: IOUAmount.h:46
-
int exponent() const noexcept
Definition: IOUAmount.h:166
-
int signum() const noexcept
Return the sign of the amount.
Definition: IOUAmount.h:160
-
std::int64_t mantissa() const noexcept
Definition: IOUAmount.h:172
+
int exponent() const noexcept
Definition: IOUAmount.h:172
+
int signum() const noexcept
Return the sign of the amount.
Definition: IOUAmount.h:166
+
std::int64_t mantissa() const noexcept
Definition: IOUAmount.h:178
A currency issued by an account.
Definition: Issue.h:36
AccountID account
Definition: Issue.h:39
Currency currency
Definition: Issue.h:38
diff --git a/ApplyContext_8cpp_source.html b/ApplyContext_8cpp_source.html index 5b6d5b541b..1ac6ab295c 100644 --- a/ApplyContext_8cpp_source.html +++ b/ApplyContext_8cpp_source.html @@ -273,7 +273,7 @@ $(function() {
bool isTesSuccess(TER x) noexcept
Definition: TER.h:672
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: View.cpp:2727
-
InvariantChecks getInvariantChecks()
get a tuple of all invariant checks
+
InvariantChecks getInvariantChecks()
get a tuple of all invariant checks
ApplyFlags
Definition: ApplyView.h:31
@ tapDRY_RUN
Definition: ApplyView.h:50
@ tapBATCH
Definition: ApplyView.h:46
diff --git a/ApplyViewBase_8cpp_source.html b/ApplyViewBase_8cpp_source.html index ad5d1be347..586181c071 100644 --- a/ApplyViewBase_8cpp_source.html +++ b/ApplyViewBase_8cpp_source.html @@ -263,7 +263,7 @@ $(function() {
virtual std::unique_ptr< txs_type::iter_base > txsEnd() const =0
virtual std::unique_ptr< sles_type::iter_base > slesBegin() const =0
virtual bool txExists(key_type const &key) const =0
Returns true if a tx exists in the tx map.
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
Definition: XRPAmount.h:43
std::shared_ptr< SLE > peek(ReadView const &base, Keylet const &k)
diff --git a/ApplyViewBase_8h_source.html b/ApplyViewBase_8h_source.html index e5eaf53001..05f9128ed1 100644 --- a/ApplyViewBase_8h_source.html +++ b/ApplyViewBase_8h_source.html @@ -210,7 +210,7 @@ $(function() {
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:144
Interface for ledger entry changes.
Definition: RawView.h:35
A view into a ledger.
Definition: ReadView.h:52
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
Definition: XRPAmount.h:43
diff --git a/Batch__test_8cpp_source.html b/Batch__test_8cpp_source.html index c26f2b3361..5283cdf18e 100644 --- a/Batch__test_8cpp_source.html +++ b/Batch__test_8cpp_source.html @@ -4008,7 +4008,7 @@ $(function() {
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:496
Application & app()
Definition: Env.h:261
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
Adds a new Batch Txn on a JTx and autofills.
Definition: batch.h:61
Set a batch nested multi-signature on a JTx.
Definition: batch.h:135
diff --git a/BookChanges__test_8cpp_source.html b/BookChanges__test_8cpp_source.html index a131bed4b9..4b5cdc4a77 100644 --- a/BookChanges__test_8cpp_source.html +++ b/BookChanges__test_8cpp_source.html @@ -241,7 +241,7 @@ $(function() {
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:460
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
Application & app()
Definition: Env.h:261
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
Set the domain on a JTx.
Definition: domain.h:30
Add a path.
Definition: paths.h:58
diff --git a/BookStep_8cpp_source.html b/BookStep_8cpp_source.html index 9b3ce2b2a1..4900f82c43 100644 --- a/BookStep_8cpp_source.html +++ b/BookStep_8cpp_source.html @@ -1652,7 +1652,7 @@ $(function() {
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
NetClock::time_point parentCloseTime() const
Returns the close time of the previous ledger.
Definition: ReadView.h:112
virtual Rules const & rules() const =0
Returns the tx processing rules.
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
Definition: STAmount.h:50
static std::uint64_t const uRateOne
Definition: STAmount.h:80
@@ -1663,7 +1663,7 @@ $(function() {
Definition: OfferStream.h:40
std::uint32_t count() const
Definition: OfferStream.h:64
-
Definition: Offer.h:52
+
Definition: Offer.h:53
Definition: XRPAmount.h:43
Rules const & rules() const override
Returns the tx processing rules.
diff --git a/Book__test_8cpp_source.html b/Book__test_8cpp_source.html index 3d441e1f0b..ad30f137e8 100644 --- a/Book__test_8cpp_source.html +++ b/Book__test_8cpp_source.html @@ -2140,7 +2140,7 @@ $(function() {
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:264
Account const & master
Definition: Env.h:125
Application & app()
Definition: Env.h:261
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
Account carol
diff --git a/CachedView_8h_source.html b/CachedView_8h_source.html index c7673226b2..31bb8cb6eb 100644 --- a/CachedView_8h_source.html +++ b/CachedView_8h_source.html @@ -280,7 +280,7 @@ $(function() {
virtual std::unique_ptr< sles_type::iter_base > slesBegin() const =0
virtual tx_type txRead(key_type const &key) const =0
Read a transaction from the tx map.
virtual bool txExists(key_type const &key) const =0
Returns true if a tx exists in the tx map.
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
Map/cache combination.
Definition: TaggedCache.h:62
Definition: CachedView.h:36
diff --git a/Check__test_8cpp_source.html b/Check__test_8cpp_source.html index a2d1f0d6e0..1246037887 100644 --- a/Check__test_8cpp_source.html +++ b/Check__test_8cpp_source.html @@ -2855,7 +2855,7 @@ $(function() {
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:460
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:331
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
Set DestinationTag on a JTx.
Definition: Check_test.cpp:68
std::uint32_t const tag_
Definition: Check_test.cpp:70
dest_tag(std::uint32_t tag)
Definition: Check_test.cpp:73
diff --git a/ConsensusTypes_8h_source.html b/ConsensusTypes_8h_source.html index 4b5e25d93a..0fcf14b5ea 100644 --- a/ConsensusTypes_8h_source.html +++ b/ConsensusTypes_8h_source.html @@ -201,10 +201,10 @@ $(function() {
185};
186
188enum class ConsensusState {
-
189 No,
+
189 No,
190 MovedOn,
191 Expired,
-
192 Yes
+
192 Yes
193};
194
203template <class Traits>
@@ -267,8 +267,6 @@ $(function() {
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
-
@ Yes
-
@ No
ConsensusMode
Represents how a node currently participates in Consensus.
@ wrongLedger
We have the wrong ledger and are attempting to acquire it.
@ proposing
We are normal participant in consensus and propose our position.
@@ -283,6 +281,8 @@ $(function() {
@ MovedOn
The network has consensus without us.
@ No
We do not have consensus.
@ accepted
Manifest is valid.
+
@ Yes
+
@ No
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
STL namespace.
diff --git a/CreateOffer_8cpp_source.html b/CreateOffer_8cpp_source.html index ad4c0c6b04..7fa23a5e17 100644 --- a/CreateOffer_8cpp_source.html +++ b/CreateOffer_8cpp_source.html @@ -1568,10 +1568,10 @@ $(function() {
Definition: TER.h:409
bool step()
Advance to the next valid offer.
TOffer< TIn, TOut > & tip() const
Returns the offer at the tip of the order book.
Definition: OfferStream.h:108
-
Definition: Offer.h:52
-
Quality quality() const noexcept
Returns the quality of the offer.
Definition: Offer.h:77
-
AccountID const & owner() const
Returns the account id of the offer's owner.
Definition: Offer.h:84
-
TAmounts< TIn, TOut > const & amount() const
Returns the in and out amounts.
Definition: Offer.h:93
+
Definition: Offer.h:53
+
Quality quality() const noexcept
Returns the quality of the offer.
Definition: Offer.h:78
+
AccountID const & owner() const
Returns the account id of the offer's owner.
Definition: Offer.h:85
+
TAmounts< TIn, TOut > const & amount() const
Returns the in and out amounts.
Definition: Offer.h:94
Definition: Taker.h:240
std::uint32_t get_bridge_crossings() const
Definition: Taker.h:273
std::uint32_t get_direct_crossings() const
Definition: Taker.h:267
diff --git a/CreateOffer_8h_source.html b/CreateOffer_8h_source.html index 85940988fd..675c27da99 100644 --- a/CreateOffer_8h_source.html +++ b/CreateOffer_8h_source.html @@ -266,7 +266,7 @@ $(function() {
Definition: STAmount.h:50
Discardable, editable view to a ledger.
Definition: Sandbox.h:35
Definition: TER.h:409
-
Definition: Offer.h:52
+
Definition: Offer.h:53
Definition: Taker.h:240
Definition: Transactor.h:138
ConsequencesFactoryType
Definition: Transactor.h:153
diff --git a/DeliveredAmount__test_8cpp_source.html b/DeliveredAmount__test_8cpp_source.html index 0771845fae..10fe71a715 100644 --- a/DeliveredAmount__test_8cpp_source.html +++ b/DeliveredAmount__test_8cpp_source.html @@ -449,7 +449,7 @@ $(function() {
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:264
Application & app()
Definition: Env.h:261
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
A balance matches.
Definition: balance.h:39
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:35
diff --git a/DepositAuth__test_8cpp_source.html b/DepositAuth__test_8cpp_source.html index 8b715a977d..4fe9a551c9 100644 --- a/DepositAuth__test_8cpp_source.html +++ b/DepositAuth__test_8cpp_source.html @@ -1683,7 +1683,7 @@ $(function() {
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:264
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:284
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition: Env.cpp:179
void memoize(Account const &account)
Associate AccountID with account.
Definition: Env.cpp:152
diff --git a/DepositAuthorized__test_8cpp_source.html b/DepositAuthorized__test_8cpp_source.html index 9a3e681258..2d8d894838 100644 --- a/DepositAuthorized__test_8cpp_source.html +++ b/DepositAuthorized__test_8cpp_source.html @@ -740,7 +740,7 @@ $(function() {
A transaction testing environment.
Definition: Env.h:121
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:331
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
@ arrayValue
array value (ordered list)
Definition: json_value.h:45
diff --git a/DirectStep_8cpp_source.html b/DirectStep_8cpp_source.html index 035f5610f7..93c0e93814 100644 --- a/DirectStep_8cpp_source.html +++ b/DirectStep_8cpp_source.html @@ -1143,8 +1143,8 @@ $(function() {
std::pair< std::optional< Quality >, DebtDirection > qualityUpperBound(ReadView const &v, DebtDirection dir) const override
Definition: DirectStep.cpp:841
std::pair< bool, EitherAmount > validFwd(PaymentSandbox &sb, ApplyView &afView, EitherAmount const &in) override
Definition: DirectStep.cpp:714
Floating point representation of amounts with high dynamic range.
Definition: IOUAmount.h:46
-
int exponent() const noexcept
Definition: IOUAmount.h:166
-
std::int64_t mantissa() const noexcept
Definition: IOUAmount.h:172
+
int exponent() const noexcept
Definition: IOUAmount.h:172
+
std::int64_t mantissa() const noexcept
Definition: IOUAmount.h:178
A currency issued by an account.
Definition: Issue.h:36
A wrapper which makes credits unavailable to balances.
A view into a ledger.
Definition: ReadView.h:52
diff --git a/Env_8cpp_source.html b/Env_8cpp_source.html index 0735214ef1..f1b421cbd9 100644 --- a/Env_8cpp_source.html +++ b/Env_8cpp_source.html @@ -732,29 +732,29 @@ $(function() {
std::string const & name() const
Return the name.
Definition: Account.h:83
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition: Env.cpp:111
void disableFeature(uint256 const feature)
Definition: Env.cpp:603
-
bool parseFailureExpected_
Definition: Env.h:725
+
bool parseFailureExpected_
Definition: Env.h:731
static ParsedResult parseResult(Json::Value const &jr)
Gets the TER result and didApply flag from a RPC Json result object.
Definition: Env.cpp:281
std::uint32_t ownerCount(Account const &account) const
Return the number of objects owned by an account.
Definition: Env.cpp:203
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:212
-
std::unordered_map< AccountID, Account > map_
Definition: Env.h:768
-
TER ter_
Definition: Env.h:724
+
std::unordered_map< AccountID, Account > map_
Definition: Env.h:774
+
TER ter_
Definition: Env.h:730
beast::unit_test::suite & test
Definition: Env.h:123
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:460
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:331
void postconditions(JTx const &jt, ParsedResult const &parsed, Json::Value const &jr=Json::Value())
Check expected postconditions of JTx submission.
Definition: Env.cpp:387
-
int trace_
Definition: Env.h:721
+
int trace_
Definition: Env.h:727
void sign_and_submit(JTx const &jt, Json::Value params=Json::nullValue)
Use the submit RPC command with a provided JTx object.
Definition: Env.cpp:349
virtual void autofill(JTx &jt)
Definition: Env.cpp:491
AbstractClient & client()
Returns the connected client.
Definition: Env.h:291
void autofill_sig(JTx &jt)
Definition: Env.cpp:466
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:264
Json::Value do_rpc(unsigned apiVersion, std::vector< std::string > const &args, std::unordered_map< std::string, std::string > const &headers={})
Definition: Env.cpp:572
-
uint256 txid_
Definition: Env.h:723
+
uint256 txid_
Definition: Env.h:729
std::shared_ptr< STTx const > st(JTx const &jt)
Create a STTx from a JTx The framework requires that JSON is valid.
Definition: Env.cpp:520
void enableFeature(uint256 const feature)
Definition: Env.cpp:595
Account const & master
Definition: Env.h:125
Account const & lookup(AccountID const &id) const
Returns the Account given the AccountID.
Definition: Env.cpp:158
-
unsigned retries_
Definition: Env.h:726
+
unsigned retries_
Definition: Env.h:732
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:496
std::shared_ptr< STTx const > ust(JTx const &jt)
Create a STTx from a JTx without sanitizing Use to inject bogus values into test transactions by firs...
Definition: Env.cpp:546
Application & app()
Definition: Env.h:261
diff --git a/Env_8h_source.html b/Env_8h_source.html index 1bbb7106b5..8cc22dd0d1 100644 --- a/Env_8h_source.html +++ b/Env_8h_source.html @@ -513,148 +513,154 @@ $(function() {
622 void
623 disableFeature(uint256 const feature);
624
-
625private:
-
626 void
-
627 fund(bool setDefaultRipple, STAmount const& amount, Account const& account);
-
628
-
629 void
-
630 fund_arg(STAmount const& amount, Account const& account)
-
631 {
-
632 fund(true, amount, account);
-
633 }
+
625 bool
+
626 enabled(uint256 feature) const
+
627 {
+
628 return current()->rules().enabled(feature);
+
629 }
+
630
+
631private:
+
632 void
+
633 fund(bool setDefaultRipple, STAmount const& amount, Account const& account);
634
-
635 template <std::size_t N>
-
636 void
-
637 fund_arg(STAmount const& amount, std::array<Account, N> const& list)
-
638 {
-
639 for (auto const& account : list)
-
640 fund(false, amount, account);
-
641 }
-
642
-
643public:
-
670 template <class Arg, class... Args>
-
671 void
-
672 fund(STAmount const& amount, Arg const& arg, Args const&... args)
-
673 {
-
674 fund_arg(amount, arg);
-
675 if constexpr (sizeof...(args) > 0)
-
676 fund(amount, args...);
-
677 }
-
678
-
697 void
-
698 trust(STAmount const& amount, Account const& account);
-
699
-
700 template <class... Accounts>
-
701 void
-
702 trust(
-
703 STAmount const& amount,
-
704 Account const& to0,
-
705 Account const& to1,
-
706 Accounts const&... toN)
-
707 {
-
708 trust(amount, to0);
-
709 trust(amount, to1, toN...);
-
710 }
-
717 std::shared_ptr<STTx const>
-
718 ust(JTx const& jt);
-
719
-
720protected:
-
721 int trace_ = 0;
-
722 TestStopwatch stopwatch_;
-
723 uint256 txid_;
-
724 TER ter_ = tesSUCCESS;
-
725 bool parseFailureExpected_ = false;
-
726 unsigned retries_ = 5;
-
727
-
728 Json::Value
-
729 do_rpc(
-
730 unsigned apiVersion,
-
731 std::vector<std::string> const& args,
-
732 std::unordered_map<std::string, std::string> const& headers = {});
+
635 void
+
636 fund_arg(STAmount const& amount, Account const& account)
+
637 {
+
638 fund(true, amount, account);
+
639 }
+
640
+
641 template <std::size_t N>
+
642 void
+
643 fund_arg(STAmount const& amount, std::array<Account, N> const& list)
+
644 {
+
645 for (auto const& account : list)
+
646 fund(false, amount, account);
+
647 }
+
648
+
649public:
+
676 template <class Arg, class... Args>
+
677 void
+
678 fund(STAmount const& amount, Arg const& arg, Args const&... args)
+
679 {
+
680 fund_arg(amount, arg);
+
681 if constexpr (sizeof...(args) > 0)
+
682 fund(amount, args...);
+
683 }
+
684
+
703 void
+
704 trust(STAmount const& amount, Account const& account);
+
705
+
706 template <class... Accounts>
+
707 void
+
708 trust(
+
709 STAmount const& amount,
+
710 Account const& to0,
+
711 Account const& to1,
+
712 Accounts const&... toN)
+
713 {
+
714 trust(amount, to0);
+
715 trust(amount, to1, toN...);
+
716 }
+
723 std::shared_ptr<STTx const>
+
724 ust(JTx const& jt);
+
725
+
726protected:
+
727 int trace_ = 0;
+
728 TestStopwatch stopwatch_;
+
729 uint256 txid_;
+
730 TER ter_ = tesSUCCESS;
+
731 bool parseFailureExpected_ = false;
+
732 unsigned retries_ = 5;
733
-
734 void
-
735 autofill_sig(JTx& jt);
-
736
-
737 virtual void
-
738 autofill(JTx& jt);
+
734 Json::Value
+
735 do_rpc(
+
736 unsigned apiVersion,
+
737 std::vector<std::string> const& args,
+
738 std::unordered_map<std::string, std::string> const& headers = {});
739
-
747 std::shared_ptr<STTx const>
-
748 st(JTx const& jt);
-
749
-
750 // Invoke funclets on stx
-
751 // Note: The STTx may not be modified
-
752 template <class... FN>
-
753 void
-
754 invoke(STTx& stx, FN const&... fN)
-
755 {
-
756 (fN(*this, stx), ...);
-
757 }
-
758
-
759 // Invoke funclets on jt
-
760 template <class... FN>
-
761 void
-
762 invoke(JTx& jt, FN const&... fN)
-
763 {
-
764 (fN(*this, jt), ...);
-
765 }
-
766
-
767 // Map of account IDs to Account
-
768 std::unordered_map<AccountID, Account> map_;
-
769};
-
770
-
771template <class... Args>
-
772Json::Value
-
773Env::rpc(
-
774 unsigned apiVersion,
-
775 std::unordered_map<std::string, std::string> const& headers,
-
776 std::string const& cmd,
-
777 Args&&... args)
-
778{
-
779 return do_rpc(
-
780 apiVersion,
-
781 std::vector<std::string>{cmd, std::forward<Args>(args)...},
-
782 headers);
-
783}
-
784
-
785template <class... Args>
-
786Json::Value
-
787Env::rpc(unsigned apiVersion, std::string const& cmd, Args&&... args)
-
788{
-
789 return rpc(
-
790 apiVersion,
-
791 std::unordered_map<std::string, std::string>(),
-
792 cmd,
-
793 std::forward<Args>(args)...);
-
794}
-
795
-
796template <class... Args>
-
797Json::Value
-
798Env::rpc(
-
799 std::unordered_map<std::string, std::string> const& headers,
-
800 std::string const& cmd,
-
801 Args&&... args)
-
802{
-
803 return do_rpc(
-
804 RPC::apiCommandLineVersion,
-
805 std::vector<std::string>{cmd, std::forward<Args>(args)...},
-
806 headers);
-
807}
-
808
-
809template <class... Args>
-
810Json::Value
-
811Env::rpc(std::string const& cmd, Args&&... args)
-
812{
-
813 return rpc(
-
814 std::unordered_map<std::string, std::string>(),
-
815 cmd,
-
816 std::forward<Args>(args)...);
-
817}
-
818
-
819} // namespace jtx
-
820} // namespace test
-
821} // namespace ripple
-
822
-
823#endif
+
740 void
+
741 autofill_sig(JTx& jt);
+
742
+
743 virtual void
+
744 autofill(JTx& jt);
+
745
+
753 std::shared_ptr<STTx const>
+
754 st(JTx const& jt);
+
755
+
756 // Invoke funclets on stx
+
757 // Note: The STTx may not be modified
+
758 template <class... FN>
+
759 void
+
760 invoke(STTx& stx, FN const&... fN)
+
761 {
+
762 (fN(*this, stx), ...);
+
763 }
+
764
+
765 // Invoke funclets on jt
+
766 template <class... FN>
+
767 void
+
768 invoke(JTx& jt, FN const&... fN)
+
769 {
+
770 (fN(*this, jt), ...);
+
771 }
+
772
+
773 // Map of account IDs to Account
+
774 std::unordered_map<AccountID, Account> map_;
+
775};
+
776
+
777template <class... Args>
+
778Json::Value
+
779Env::rpc(
+
780 unsigned apiVersion,
+
781 std::unordered_map<std::string, std::string> const& headers,
+
782 std::string const& cmd,
+
783 Args&&... args)
+
784{
+
785 return do_rpc(
+
786 apiVersion,
+
787 std::vector<std::string>{cmd, std::forward<Args>(args)...},
+
788 headers);
+
789}
+
790
+
791template <class... Args>
+
792Json::Value
+
793Env::rpc(unsigned apiVersion, std::string const& cmd, Args&&... args)
+
794{
+
795 return rpc(
+
796 apiVersion,
+
797 std::unordered_map<std::string, std::string>(),
+
798 cmd,
+
799 std::forward<Args>(args)...);
+
800}
+
801
+
802template <class... Args>
+
803Json::Value
+
804Env::rpc(
+
805 std::unordered_map<std::string, std::string> const& headers,
+
806 std::string const& cmd,
+
807 Args&&... args)
+
808{
+
809 return do_rpc(
+
810 RPC::apiCommandLineVersion,
+
811 std::vector<std::string>{cmd, std::forward<Args>(args)...},
+
812 headers);
+
813}
+
814
+
815template <class... Args>
+
816Json::Value
+
817Env::rpc(std::string const& cmd, Args&&... args)
+
818{
+
819 return rpc(
+
820 std::unordered_map<std::string, std::string>(),
+
821 cmd,
+
822 std::forward<Args>(args)...);
+
823}
+
824
+
825} // namespace jtx
+
826} // namespace test
+
827} // namespace ripple
+
828
+
829#endif
Represents a JSON value.
Definition: json_value.h:150
@@ -680,20 +686,20 @@ $(function() {
Immutable cryptographic account descriptor.
Definition: Account.h:39
static Account const master
The master account.
Definition: Account.h:48
A transaction testing environment.
Definition: Env.h:121
-
void fund_arg(STAmount const &amount, std::array< Account, N > const &list)
Definition: Env.h:637
+
void fund_arg(STAmount const &amount, std::array< Account, N > const &list)
Definition: Env.h:643
Json::Value json(JsonValue &&jv, FN const &... fN)
Create JSON from parameters.
Definition: Env.h:522
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition: Env.cpp:111
void trace(int howMany=-1)
Turn on JSON tracing.
Definition: Env.h:403
void disableFeature(uint256 const feature)
Definition: Env.cpp:603
-
bool parseFailureExpected_
Definition: Env.h:725
+
bool parseFailureExpected_
Definition: Env.h:731
static ParsedResult parseResult(Json::Value const &jr)
Gets the TER result and didApply flag from a RPC Json result object.
Definition: Env.cpp:281
Env(beast::unit_test::suite &suite_, std::unique_ptr< Config > config, std::unique_ptr< Logs > logs=nullptr, beast::severities::Severity thresh=beast::severities::kError)
Create Env using suite and Config pointer.
Definition: Env.h:233
std::uint32_t ownerCount(Account const &account) const
Return the number of objects owned by an account.
Definition: Env.cpp:203
-
std::unordered_map< AccountID, Account > map_
Definition: Env.h:768
+
std::unordered_map< AccountID, Account > map_
Definition: Env.h:774
void notrace()
Turn off JSON tracing.
Definition: Env.h:410
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:535
-
void trust(STAmount const &amount, Account const &to0, Account const &to1, Accounts const &... toN)
Definition: Env.h:702
-
TER ter_
Definition: Env.h:724
+
void trust(STAmount const &amount, Account const &to0, Account const &to1, Accounts const &... toN)
Definition: Env.h:708
+
TER ter_
Definition: Env.h:730
unsigned retries() const
Definition: Env.h:437
TER ter() const
Return the TER for the last JTx.
Definition: Env.h:586
Env()=delete
@@ -702,41 +708,42 @@ $(function() {
bool close(std::chrono::duration< Rep, Period > const &elapsed)
Close and advance the ledger.
Definition: Env.h:379
AppBundle bundle_
Definition: Env.h:159
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:460
-
void invoke(JTx &jt, FN const &... fN)
Definition: Env.h:762
+
void invoke(JTx &jt, FN const &... fN)
Definition: Env.h:768
Env & operator=(Env const &)=delete
Env & operator()(JsonValue &&jv, FN const &... fN)
Definition: Env.h:578
Env(Env const &)=delete
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:331
void postconditions(JTx const &jt, ParsedResult const &parsed, Json::Value const &jr=Json::Value())
Check expected postconditions of JTx submission.
Definition: Env.cpp:387
-
int trace_
Definition: Env.h:721
+
int trace_
Definition: Env.h:727
+
bool enabled(uint256 feature) const
Definition: Env.h:626
void sign_and_submit(JTx const &jt, Json::Value params=Json::nullValue)
Use the submit RPC command with a provided JTx object.
Definition: Env.cpp:349
AbstractClient & client()
Returns the connected client.
Definition: Env.h:291
void autofill_sig(JTx &jt)
Definition: Env.cpp:466
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:264
-
void fund(STAmount const &amount, Arg const &arg, Args const &... args)
Create a new account with some XRP.
Definition: Env.h:672
+
void fund(STAmount const &amount, Arg const &arg, Args const &... args)
Create a new account with some XRP.
Definition: Env.h:678
Json::Value do_rpc(unsigned apiVersion, std::vector< std::string > const &args, std::unordered_map< std::string, std::string > const &headers={})
Definition: Env.cpp:572
-
uint256 txid_
Definition: Env.h:723
-
void invoke(STTx &stx, FN const &... fN)
Definition: Env.h:754
+
uint256 txid_
Definition: Env.h:729
+
void invoke(STTx &stx, FN const &... fN)
Definition: Env.h:760
std::shared_ptr< STTx const > st(JTx const &jt)
Create a STTx from a JTx The framework requires that JSON is valid.
Definition: Env.cpp:520
void enableFeature(uint256 const feature)
Definition: Env.cpp:595
Env(beast::unit_test::suite &suite_)
Create Env with only the current test suite.
Definition: Env.h:254
Account const & master
Definition: Env.h:125
Account const & lookup(AccountID const &id) const
Returns the Account given the AccountID.
Definition: Env.cpp:158
-
unsigned retries_
Definition: Env.h:726
+
unsigned retries_
Definition: Env.h:732
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:496
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:284
std::shared_ptr< STTx const > ust(JTx const &jt)
Create a STTx from a JTx without sanitizing Use to inject bogus values into test transactions by firs...
Definition: Env.cpp:546
Application & app()
Definition: Env.h:261
beast::Journal const journal
Definition: Env.h:162
ManualTimeKeeper & timeKeeper()
Definition: Env.h:273
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
virtual void submit(JTx const &jt)
Submit an existing JTx.
Definition: Env.cpp:319
Env & apply(JsonValue &&jv, FN const &... fN)
Apply funclets and submit.
Definition: Env.h:570
void disable_sigs()
Turn off signature checks.
Definition: Env.h:423
void set_retries(unsigned r=5)
Definition: Env.h:430
bool close()
Close and advance the ledger.
Definition: Env.h:393
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
-
TestStopwatch stopwatch_
Definition: Env.h:722
+
TestStopwatch stopwatch_
Definition: Env.h:728
Application const & app() const
Definition: Env.h:267
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition: Env.cpp:447
JTx jtnofill(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:508
@@ -744,7 +751,7 @@ $(function() {
void memoize(Account const &account)
Associate AccountID with account.
Definition: Env.cpp:152
Env(beast::unit_test::suite &suite_, std::unique_ptr< Config > config, FeatureBitset features, std::unique_ptr< Logs > logs=nullptr, beast::severities::Severity thresh=beast::severities::kError)
Create Env using suite, Config pointer, and explicit features.
Definition: Env.h:184
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition: Env.cpp:221
-
void fund_arg(STAmount const &amount, Account const &account)
Definition: Env.h:630
+
void fund_arg(STAmount const &amount, Account const &account)
Definition: Env.h:636
Env(beast::unit_test::suite &suite_, FeatureBitset features, std::unique_ptr< Logs > logs=nullptr)
Create Env with default config and specified features.
Definition: Env.h:214
Definition: Env.h:97
SuiteLogs(beast::unit_test::suite &suite)
Definition: Env.h:101
diff --git a/Env__test_8cpp_source.html b/Env__test_8cpp_source.html index 2d9ec32810..05bc6345a4 100644 --- a/Env__test_8cpp_source.html +++ b/Env__test_8cpp_source.html @@ -1085,7 +1085,7 @@ $(function() {
Account const & master
Definition: Env.h:125
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:496
Application & app()
Definition: Env.h:261
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition: Env.cpp:179
void memoize(Account const &account)
Associate AccountID with account.
Definition: Env.cpp:152
diff --git a/FeeVoteImpl_8cpp_source.html b/FeeVoteImpl_8cpp_source.html index d4f460a4e8..d664bc9d58 100644 --- a/FeeVoteImpl_8cpp_source.html +++ b/FeeVoteImpl_8cpp_source.html @@ -432,7 +432,7 @@ $(function() {
FeeVoteImpl(FeeSetup const &setup, beast::Journal journal)
beast::Journal const journal_
Definition: FeeVoteImpl.cpp:96
Manager to process fee votes.
Definition: FeeVote.h:32
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
Definition: STTx.h:48
Definition: STValidation.h:47
diff --git a/FeeVote_8h_source.html b/FeeVote_8h_source.html index 5b10731191..0e121f3d98 100644 --- a/FeeVote_8h_source.html +++ b/FeeVote_8h_source.html @@ -135,7 +135,7 @@ $(function() {
virtual void doVoting(std::shared_ptr< ReadView const > const &lastClosedLedger, std::vector< std::shared_ptr< STValidation > > const &parentValidations, std::shared_ptr< SHAMap > const &initialPosition)=0
Cast our local vote on the fee.
virtual ~FeeVote()=default
virtual void doValidation(Fees const &lastFees, Rules const &rules, STValidation &val)=0
Add local fee preference to validation.
-
Rules controlling protocol behavior.
Definition: Rules.h:35
+
Rules controlling protocol behavior.
Definition: Rules.h:38
Definition: STValidation.h:47
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::unique_ptr< FeeVote > make_FeeVote(FeeSetup const &setup, beast::Journal journal)
Create an instance of the FeeVote logic.
diff --git a/FixNFTokenPageLinks__test_8cpp_source.html b/FixNFTokenPageLinks__test_8cpp_source.html index a886bf0e64..2eb2eefc1f 100644 --- a/FixNFTokenPageLinks__test_8cpp_source.html +++ b/FixNFTokenPageLinks__test_8cpp_source.html @@ -766,7 +766,7 @@ $(function() {
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:212
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:331
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
-
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:773
+
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition: Env.cpp:221
T end(T... args)
T erase(T... args)
diff --git a/IOUAmount_8cpp_source.html b/IOUAmount_8cpp_source.html index 75674a5481..57561cde92 100644 --- a/IOUAmount_8cpp_source.html +++ b/IOUAmount_8cpp_source.html @@ -404,10 +404,10 @@ $(function() {
Floating point representation of amounts with high dynamic range.
Definition: IOUAmount.h:46
-
int exponent() const noexcept
Definition: IOUAmount.h:166
+
int exponent() const noexcept
Definition: IOUAmount.h:172
std::int64_t mantissa_
Definition: IOUAmount.h:48
void normalize()
Adjusts the mantissa and exponent to the proper range.
Definition: IOUAmount.cpp:75
-
std::int64_t mantissa() const noexcept
Definition: IOUAmount.h:172
+
std::int64_t mantissa() const noexcept
Definition: IOUAmount.h:178
IOUAmount & operator+=(IOUAmount const &other)
Definition: IOUAmount.cpp:138
IOUAmount()=default
static IOUAmount minPositiveAmount()
Definition: IOUAmount.cpp:69
diff --git a/IOUAmount_8h_source.html b/IOUAmount_8h_source.html index 7cf88d1217..b6cd8df71a 100644 --- a/IOUAmount_8h_source.html +++ b/IOUAmount_8h_source.html @@ -158,155 +158,160 @@ $(function() {
98
99 static IOUAmount
100 minPositiveAmount();
-
101};
-
102
-
103inline IOUAmount::IOUAmount(beast::Zero)
-
104{
-
105 *this = beast::zero;
-
106}
-
107
-
108inline IOUAmount::IOUAmount(std::int64_t mantissa, int exponent)
-
109 : mantissa_(mantissa), exponent_(exponent)
+
101
+
102 friend std::ostream&
+
103 operator<<(std::ostream& os, IOUAmount const& x)
+
104 {
+
105 return os << to_string(x);
+
106 }
+
107};
+
108
+
109inline IOUAmount::IOUAmount(beast::Zero)
110{
-
111 normalize();
+
111 *this = beast::zero;
112}
113
-
114inline IOUAmount&
-
115IOUAmount::operator=(beast::Zero)
+
114inline IOUAmount::IOUAmount(std::int64_t mantissa, int exponent)
+
115 : mantissa_(mantissa), exponent_(exponent)
116{
-
117 // The -100 is used to allow 0 to sort less than small positive values
-
118 // which will have a large negative exponent.
-
119 mantissa_ = 0;
-
120 exponent_ = -100;
-
121 return *this;
-
122}
-
123
-
124inline IOUAmount::operator Number() const
-
125{
-
126 return Number{mantissa_, exponent_};
-
127}
-
128
-
129inline IOUAmount&
-
130IOUAmount::operator-=(IOUAmount const& other)
-
131{
-
132 *this += -other;
-
133 return *this;
-
134}
-
135
-
136inline IOUAmount
-
137IOUAmount::operator-() const
-
138{
-
139 return {-mantissa_, exponent_};
+
117 normalize();
+
118}
+
119
+
120inline IOUAmount&
+
121IOUAmount::operator=(beast::Zero)
+
122{
+
123 // The -100 is used to allow 0 to sort less than small positive values
+
124 // which will have a large negative exponent.
+
125 mantissa_ = 0;
+
126 exponent_ = -100;
+
127 return *this;
+
128}
+
129
+
130inline IOUAmount::operator Number() const
+
131{
+
132 return Number{mantissa_, exponent_};
+
133}
+
134
+
135inline IOUAmount&
+
136IOUAmount::operator-=(IOUAmount const& other)
+
137{
+
138 *this += -other;
+
139 return *this;
140}
141
-
142inline bool
-
143IOUAmount::operator==(IOUAmount const& other) const
+
142inline IOUAmount
+
143IOUAmount::operator-() const
144{
-
145 return exponent_ == other.exponent_ && mantissa_ == other.mantissa_;
+
145 return {-mantissa_, exponent_};
146}
147
-
148inline bool
-
149IOUAmount::operator<(IOUAmount const& other) const
+
148inline bool
+
149IOUAmount::operator==(IOUAmount const& other) const
150{
-
151 return Number{*this} < Number{other};
+
151 return exponent_ == other.exponent_ && mantissa_ == other.mantissa_;
152}
153
-
154inline IOUAmount::operator bool() const noexcept
-
155{
-
156 return mantissa_ != 0;
-
157}
-
158
-
159inline int
-
160IOUAmount::signum() const noexcept
+
154inline bool
+
155IOUAmount::operator<(IOUAmount const& other) const
+
156{
+
157 return Number{*this} < Number{other};
+
158}
+
159
+
160inline IOUAmount::operator bool() const noexcept
161{
-
162 return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0);
+
162 return mantissa_ != 0;
163}
164
165inline int
-
166IOUAmount::exponent() const noexcept
+
166IOUAmount::signum() const noexcept
167{
-
168 return exponent_;
+
168 return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0);
169}
170
-
171inline std::int64_t
-
172IOUAmount::mantissa() const noexcept
+
171inline int
+
172IOUAmount::exponent() const noexcept
173{
-
174 return mantissa_;
+
174 return exponent_;
175}
176
-
177std::string
-
178to_string(IOUAmount const& amount);
-
179
-
180/* Return num*amt/den
-
181 This function keeps more precision than computing
-
182 num*amt, storing the result in an IOUAmount, then
-
183 dividing by den.
-
184*/
-
185IOUAmount
-
186mulRatio(
-
187 IOUAmount const& amt,
-
188 std::uint32_t num,
-
189 std::uint32_t den,
-
190 bool roundUp);
-
191
-
192// Since many uses of the number class do not have access to a ledger,
-
193// getSTNumberSwitchover needs to be globally accessible.
-
194
-
195bool
-
196getSTNumberSwitchover();
+
177inline std::int64_t
+
178IOUAmount::mantissa() const noexcept
+
179{
+
180 return mantissa_;
+
181}
+
182
+
183std::string
+
184to_string(IOUAmount const& amount);
+
185
+
186/* Return num*amt/den
+
187 This function keeps more precision than computing
+
188 num*amt, storing the result in an IOUAmount, then
+
189 dividing by den.
+
190*/
+
191IOUAmount
+
192mulRatio(
+
193 IOUAmount const& amt,
+
194 std::uint32_t num,
+
195 std::uint32_t den,
+
196 bool roundUp);
197
-
198void
-
199setSTNumberSwitchover(bool v);
+
198// Since many uses of the number class do not have access to a ledger,
+
199// getSTNumberSwitchover needs to be globally accessible.
200
-
204class NumberSO
-
205{
-
206 bool saved_;
-
207
-
208public:
-
209 ~NumberSO()
-
210 {
-
211 setSTNumberSwitchover(saved_);
-
212 }
+
201bool
+
202getSTNumberSwitchover();
+
203
+
204void
+
205setSTNumberSwitchover(bool v);
+
206
+
210class NumberSO
+
211{
+
212 bool saved_;
213
-
214 NumberSO(NumberSO const&) = delete;
-
215 NumberSO&
-
216 operator=(NumberSO const&) = delete;
-
217
-
218 explicit NumberSO(bool v) : saved_(getSTNumberSwitchover())
-
219 {
-
220 setSTNumberSwitchover(v);
-
221 }
-
222};
+
214public:
+
215 ~NumberSO()
+
216 {
+
217 setSTNumberSwitchover(saved_);
+
218 }
+
219
+
220 NumberSO(NumberSO const&) = delete;
+
221 NumberSO&
+
222 operator=(NumberSO const&) = delete;
223
-
224} // namespace ripple
-
225
-
226#endif
+
224 explicit NumberSO(bool v) : saved_(getSTNumberSwitchover())
+
225 {
+
226 setSTNumberSwitchover(v);
+
227 }
+
228};
+
229
+
230} // namespace ripple
+
231
+
232#endif
Floating point representation of amounts with high dynamic range.
Definition: IOUAmount.h:46
-
IOUAmount operator-() const
Definition: IOUAmount.h:137
-
int exponent() const noexcept
Definition: IOUAmount.h:166
+
IOUAmount operator-() const
Definition: IOUAmount.h:143
+
int exponent() const noexcept
Definition: IOUAmount.h:172
std::int64_t mantissa_
Definition: IOUAmount.h:48
-
bool operator<(IOUAmount const &other) const
Definition: IOUAmount.h:149
-
int signum() const noexcept
Return the sign of the amount.
Definition: IOUAmount.h:160
-
bool operator==(IOUAmount const &other) const
Definition: IOUAmount.h:143
+
bool operator<(IOUAmount const &other) const
Definition: IOUAmount.h:155
+
int signum() const noexcept
Return the sign of the amount.
Definition: IOUAmount.h:166
+
bool operator==(IOUAmount const &other) const
Definition: IOUAmount.h:149
void normalize()
Adjusts the mantissa and exponent to the proper range.
Definition: IOUAmount.cpp:75
-
std::int64_t mantissa() const noexcept
Definition: IOUAmount.h:172
-
IOUAmount & operator=(beast::Zero)
Definition: IOUAmount.h:115
+
std::int64_t mantissa() const noexcept
Definition: IOUAmount.h:178
+
IOUAmount & operator=(beast::Zero)
Definition: IOUAmount.h:121
IOUAmount & operator+=(IOUAmount const &other)
Definition: IOUAmount.cpp:138
IOUAmount()=default
static IOUAmount minPositiveAmount()
Definition: IOUAmount.cpp:69
int exponent_
Definition: IOUAmount.h:49
-
IOUAmount & operator-=(IOUAmount const &other)
Definition: IOUAmount.h:130
-
RAII class to set and restore the Number switchover.
Definition: IOUAmount.h:205
+
IOUAmount & operator-=(IOUAmount const &other)
Definition: IOUAmount.h:136
+
RAII class to set and restore the Number switchover.
Definition: IOUAmount.h:211
NumberSO & operator=(NumberSO const &)=delete
NumberSO(NumberSO const &)=delete
-
bool saved_
Definition: IOUAmount.h:206
-
NumberSO(bool v)
Definition: IOUAmount.h:218
-
~NumberSO()
Definition: IOUAmount.h:209
+
bool saved_
Definition: IOUAmount.h:212
+
NumberSO(bool v)
Definition: IOUAmount.h:224
+
~NumberSO()
Definition: IOUAmount.h:215
Definition: Number.h:36
-
Definition: base_uint.h:662
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
void setSTNumberSwitchover(bool v)
Definition: IOUAmount.cpp:56
IOUAmount mulRatio(IOUAmount const &amt, std::uint32_t num, std::uint32_t den, bool roundUp)
Definition: IOUAmount.cpp:190
diff --git a/IOUAmount__test_8cpp_source.html b/IOUAmount__test_8cpp_source.html index 715f123fbf..9607f944ee 100644 --- a/IOUAmount__test_8cpp_source.html +++ b/IOUAmount__test_8cpp_source.html @@ -368,9 +368,9 @@ $(function() {
void testBeastZero()
void testZero()
Floating point representation of amounts with high dynamic range.
Definition: IOUAmount.h:46
-
int exponent() const noexcept
Definition: IOUAmount.h:166
-
int signum() const noexcept
Return the sign of the amount.
Definition: IOUAmount.h:160
-
std::int64_t mantissa() const noexcept
Definition: IOUAmount.h:172
+
int exponent() const noexcept
Definition: IOUAmount.h:172
+
int signum() const noexcept
Return the sign of the amount.
Definition: IOUAmount.h:166
+
std::int64_t mantissa() const noexcept
Definition: IOUAmount.h:178
T max(T... args)
Definition: ValidatorList.h:38
diff --git a/InvariantCheck_8cpp_source.html b/InvariantCheck_8cpp_source.html index aafb5ce49b..d87b084d68 100644 --- a/InvariantCheck_8cpp_source.html +++ b/InvariantCheck_8cpp_source.html @@ -95,1687 +95,1997 @@ $(function() {
17*/
18//==============================================================================
19
-
20#include <xrpld/app/misc/CredentialHelpers.h>
-
21#include <xrpld/app/tx/detail/InvariantCheck.h>
-
22#include <xrpld/app/tx/detail/NFTokenUtils.h>
-
23#include <xrpld/app/tx/detail/PermissionedDomainSet.h>
-
24#include <xrpld/ledger/ReadView.h>
-
25#include <xrpld/ledger/View.h>
-
26
-
27#include <xrpl/basics/Log.h>
-
28#include <xrpl/protocol/Feature.h>
-
29#include <xrpl/protocol/FeeUnits.h>
-
30#include <xrpl/protocol/STArray.h>
-
31#include <xrpl/protocol/SystemParameters.h>
-
32#include <xrpl/protocol/TxFormats.h>
-
33#include <xrpl/protocol/nftPageMask.h>
-
34
-
35namespace ripple {
+
20#include <xrpld/app/misc/AMMHelpers.h>
+
21#include <xrpld/app/misc/AMMUtils.h>
+
22#include <xrpld/app/misc/CredentialHelpers.h>
+
23#include <xrpld/app/tx/detail/InvariantCheck.h>
+
24#include <xrpld/app/tx/detail/NFTokenUtils.h>
+
25#include <xrpld/app/tx/detail/PermissionedDomainSet.h>
+
26#include <xrpld/ledger/ReadView.h>
+
27#include <xrpld/ledger/View.h>
+
28
+
29#include <xrpl/basics/Log.h>
+
30#include <xrpl/protocol/Feature.h>
+
31#include <xrpl/protocol/FeeUnits.h>
+
32#include <xrpl/protocol/STArray.h>
+
33#include <xrpl/protocol/SystemParameters.h>
+
34#include <xrpl/protocol/TxFormats.h>
+
35#include <xrpl/protocol/nftPageMask.h>
36
-
37void
-
38TransactionFeeCheck::visitEntry(
-
39 bool,
-
40 std::shared_ptr<SLE const> const&,
-
41 std::shared_ptr<SLE const> const&)
-
42{
-
43 // nothing to do
-
44}
-
45
-
46bool
-
47TransactionFeeCheck::finalize(
-
48 STTx const& tx,
-
49 TER const,
-
50 XRPAmount const fee,
-
51 ReadView const&,
-
52 beast::Journal const& j)
-
53{
-
54 // We should never charge a negative fee
-
55 if (fee.drops() < 0)
-
56 {
-
57 JLOG(j.fatal()) << "Invariant failed: fee paid was negative: "
-
58 << fee.drops();
-
59 return false;
-
60 }
-
61
-
62 // We should never charge a fee that's greater than or equal to the
-
63 // entire XRP supply.
-
64 if (fee >= INITIAL_XRP)
-
65 {
-
66 JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: "
-
67 << fee.drops();
-
68 return false;
-
69 }
-
70
-
71 // We should never charge more for a transaction than the transaction
-
72 // authorizes. It's possible to charge less in some circumstances.
-
73 if (fee > tx.getFieldAmount(sfFee).xrp())
-
74 {
-
75 JLOG(j.fatal()) << "Invariant failed: fee paid is " << fee.drops()
-
76 << " exceeds fee specified in transaction.";
-
77 return false;
-
78 }
-
79
-
80 return true;
-
81}
-
82
-
83//------------------------------------------------------------------------------
+
37namespace ripple {
+
38
+
39void
+
40TransactionFeeCheck::visitEntry(
+
41 bool,
+
42 std::shared_ptr<SLE const> const&,
+
43 std::shared_ptr<SLE const> const&)
+
44{
+
45 // nothing to do
+
46}
+
47
+
48bool
+
49TransactionFeeCheck::finalize(
+
50 STTx const& tx,
+
51 TER const,
+
52 XRPAmount const fee,
+
53 ReadView const&,
+
54 beast::Journal const& j)
+
55{
+
56 // We should never charge a negative fee
+
57 if (fee.drops() < 0)
+
58 {
+
59 JLOG(j.fatal()) << "Invariant failed: fee paid was negative: "
+
60 << fee.drops();
+
61 return false;
+
62 }
+
63
+
64 // We should never charge a fee that's greater than or equal to the
+
65 // entire XRP supply.
+
66 if (fee >= INITIAL_XRP)
+
67 {
+
68 JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: "
+
69 << fee.drops();
+
70 return false;
+
71 }
+
72
+
73 // We should never charge more for a transaction than the transaction
+
74 // authorizes. It's possible to charge less in some circumstances.
+
75 if (fee > tx.getFieldAmount(sfFee).xrp())
+
76 {
+
77 JLOG(j.fatal()) << "Invariant failed: fee paid is " << fee.drops()
+
78 << " exceeds fee specified in transaction.";
+
79 return false;
+
80 }
+
81
+
82 return true;
+
83}
84
-
85void
-
86XRPNotCreated::visitEntry(
-
87 bool isDelete,
-
88 std::shared_ptr<SLE const> const& before,
-
89 std::shared_ptr<SLE const> const& after)
-
90{
-
91 /* We go through all modified ledger entries, looking only at account roots,
-
92 * escrow payments, and payment channels. We remove from the total any
-
93 * previous XRP values and add to the total any new XRP values. The net
-
94 * balance of a payment channel is computed from two fields (amount and
-
95 * balance) and deletions are ignored for paychan and escrow because the
-
96 * amount fields have not been adjusted for those in the case of deletion.
-
97 */
-
98 if (before)
-
99 {
-
100 switch (before->getType())
-
101 {
-
102 case ltACCOUNT_ROOT:
-
103 drops_ -= (*before)[sfBalance].xrp().drops();
-
104 break;
-
105 case ltPAYCHAN:
-
106 drops_ -=
-
107 ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
-
108 break;
-
109 case ltESCROW:
-
110 drops_ -= (*before)[sfAmount].xrp().drops();
-
111 break;
-
112 default:
+
85//------------------------------------------------------------------------------
+
86
+
87void
+
88XRPNotCreated::visitEntry(
+
89 bool isDelete,
+
90 std::shared_ptr<SLE const> const& before,
+
91 std::shared_ptr<SLE const> const& after)
+
92{
+
93 /* We go through all modified ledger entries, looking only at account roots,
+
94 * escrow payments, and payment channels. We remove from the total any
+
95 * previous XRP values and add to the total any new XRP values. The net
+
96 * balance of a payment channel is computed from two fields (amount and
+
97 * balance) and deletions are ignored for paychan and escrow because the
+
98 * amount fields have not been adjusted for those in the case of deletion.
+
99 */
+
100 if (before)
+
101 {
+
102 switch (before->getType())
+
103 {
+
104 case ltACCOUNT_ROOT:
+
105 drops_ -= (*before)[sfBalance].xrp().drops();
+
106 break;
+
107 case ltPAYCHAN:
+
108 drops_ -=
+
109 ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
+
110 break;
+
111 case ltESCROW:
+
112 drops_ -= (*before)[sfAmount].xrp().drops();
113 break;
-
114 }
-
115 }
-
116
-
117 if (after)
-
118 {
-
119 switch (after->getType())
-
120 {
-
121 case ltACCOUNT_ROOT:
-
122 drops_ += (*after)[sfBalance].xrp().drops();
-
123 break;
-
124 case ltPAYCHAN:
-
125 if (!isDelete)
-
126 drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
-
127 .xrp()
-
128 .drops();
-
129 break;
-
130 case ltESCROW:
-
131 if (!isDelete)
-
132 drops_ += (*after)[sfAmount].xrp().drops();
-
133 break;
-
134 default:
+
114 default:
+
115 break;
+
116 }
+
117 }
+
118
+
119 if (after)
+
120 {
+
121 switch (after->getType())
+
122 {
+
123 case ltACCOUNT_ROOT:
+
124 drops_ += (*after)[sfBalance].xrp().drops();
+
125 break;
+
126 case ltPAYCHAN:
+
127 if (!isDelete)
+
128 drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
+
129 .xrp()
+
130 .drops();
+
131 break;
+
132 case ltESCROW:
+
133 if (!isDelete)
+
134 drops_ += (*after)[sfAmount].xrp().drops();
135 break;
-
136 }
-
137 }
-
138}
-
139
-
140bool
-
141XRPNotCreated::finalize(
-
142 STTx const& tx,
-
143 TER const,
-
144 XRPAmount const fee,
-
145 ReadView const&,
-
146 beast::Journal const& j)
-
147{
-
148 // The net change should never be positive, as this would mean that the
-
149 // transaction created XRP out of thin air. That's not possible.
-
150 if (drops_ > 0)
-
151 {
-
152 JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: "
-
153 << drops_;
-
154 return false;
-
155 }
-
156
-
157 // The negative of the net change should be equal to actual fee charged.
-
158 if (-drops_ != fee.drops())
-
159 {
-
160 JLOG(j.fatal()) << "Invariant failed: XRP net change of " << drops_
-
161 << " doesn't match fee " << fee.drops();
-
162 return false;
-
163 }
-
164
-
165 return true;
-
166}
-
167
-
168//------------------------------------------------------------------------------
+
136 default:
+
137 break;
+
138 }
+
139 }
+
140}
+
141
+
142bool
+
143XRPNotCreated::finalize(
+
144 STTx const& tx,
+
145 TER const,
+
146 XRPAmount const fee,
+
147 ReadView const&,
+
148 beast::Journal const& j)
+
149{
+
150 // The net change should never be positive, as this would mean that the
+
151 // transaction created XRP out of thin air. That's not possible.
+
152 if (drops_ > 0)
+
153 {
+
154 JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: "
+
155 << drops_;
+
156 return false;
+
157 }
+
158
+
159 // The negative of the net change should be equal to actual fee charged.
+
160 if (-drops_ != fee.drops())
+
161 {
+
162 JLOG(j.fatal()) << "Invariant failed: XRP net change of " << drops_
+
163 << " doesn't match fee " << fee.drops();
+
164 return false;
+
165 }
+
166
+
167 return true;
+
168}
169
-
170void
-
171XRPBalanceChecks::visitEntry(
-
172 bool,
-
173 std::shared_ptr<SLE const> const& before,
-
174 std::shared_ptr<SLE const> const& after)
-
175{
-
176 auto isBad = [](STAmount const& balance) {
-
177 if (!balance.native())
-
178 return true;
-
179
-
180 auto const drops = balance.xrp();
+
170//------------------------------------------------------------------------------
+
171
+
172void
+
173XRPBalanceChecks::visitEntry(
+
174 bool,
+
175 std::shared_ptr<SLE const> const& before,
+
176 std::shared_ptr<SLE const> const& after)
+
177{
+
178 auto isBad = [](STAmount const& balance) {
+
179 if (!balance.native())
+
180 return true;
181
-
182 // Can't have more than the number of drops instantiated
-
183 // in the genesis ledger.
-
184 if (drops > INITIAL_XRP)
-
185 return true;
-
186
-
187 // Can't have a negative balance (0 is OK)
-
188 if (drops < XRPAmount{0})
-
189 return true;
-
190
-
191 return false;
-
192 };
-
193
-
194 if (before && before->getType() == ltACCOUNT_ROOT)
-
195 bad_ |= isBad((*before)[sfBalance]);
-
196
-
197 if (after && after->getType() == ltACCOUNT_ROOT)
-
198 bad_ |= isBad((*after)[sfBalance]);
-
199}
-
200
-
201bool
-
202XRPBalanceChecks::finalize(
-
203 STTx const&,
-
204 TER const,
-
205 XRPAmount const,
-
206 ReadView const&,
-
207 beast::Journal const& j)
-
208{
-
209 if (bad_)
-
210 {
-
211 JLOG(j.fatal()) << "Invariant failed: incorrect account XRP balance";
-
212 return false;
-
213 }
-
214
-
215 return true;
-
216}
-
217
-
218//------------------------------------------------------------------------------
+
182 auto const drops = balance.xrp();
+
183
+
184 // Can't have more than the number of drops instantiated
+
185 // in the genesis ledger.
+
186 if (drops > INITIAL_XRP)
+
187 return true;
+
188
+
189 // Can't have a negative balance (0 is OK)
+
190 if (drops < XRPAmount{0})
+
191 return true;
+
192
+
193 return false;
+
194 };
+
195
+
196 if (before && before->getType() == ltACCOUNT_ROOT)
+
197 bad_ |= isBad((*before)[sfBalance]);
+
198
+
199 if (after && after->getType() == ltACCOUNT_ROOT)
+
200 bad_ |= isBad((*after)[sfBalance]);
+
201}
+
202
+
203bool
+
204XRPBalanceChecks::finalize(
+
205 STTx const&,
+
206 TER const,
+
207 XRPAmount const,
+
208 ReadView const&,
+
209 beast::Journal const& j)
+
210{
+
211 if (bad_)
+
212 {
+
213 JLOG(j.fatal()) << "Invariant failed: incorrect account XRP balance";
+
214 return false;
+
215 }
+
216
+
217 return true;
+
218}
219
-
220void
-
221NoBadOffers::visitEntry(
-
222 bool isDelete,
-
223 std::shared_ptr<SLE const> const& before,
-
224 std::shared_ptr<SLE const> const& after)
-
225{
-
226 auto isBad = [](STAmount const& pays, STAmount const& gets) {
-
227 // An offer should never be negative
-
228 if (pays < beast::zero)
-
229 return true;
-
230
-
231 if (gets < beast::zero)
-
232 return true;
-
233
-
234 // Can't have an XRP to XRP offer:
-
235 return pays.native() && gets.native();
-
236 };
-
237
-
238 if (before && before->getType() == ltOFFER)
-
239 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
-
240
-
241 if (after && after->getType() == ltOFFER)
-
242 bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]);
-
243}
-
244
-
245bool
-
246NoBadOffers::finalize(
-
247 STTx const&,
-
248 TER const,
-
249 XRPAmount const,
-
250 ReadView const&,
-
251 beast::Journal const& j)
-
252{
-
253 if (bad_)
-
254 {
-
255 JLOG(j.fatal()) << "Invariant failed: offer with a bad amount";
-
256 return false;
-
257 }
-
258
-
259 return true;
-
260}
-
261
-
262//------------------------------------------------------------------------------
+
220//------------------------------------------------------------------------------
+
221
+
222void
+
223NoBadOffers::visitEntry(
+
224 bool isDelete,
+
225 std::shared_ptr<SLE const> const& before,
+
226 std::shared_ptr<SLE const> const& after)
+
227{
+
228 auto isBad = [](STAmount const& pays, STAmount const& gets) {
+
229 // An offer should never be negative
+
230 if (pays < beast::zero)
+
231 return true;
+
232
+
233 if (gets < beast::zero)
+
234 return true;
+
235
+
236 // Can't have an XRP to XRP offer:
+
237 return pays.native() && gets.native();
+
238 };
+
239
+
240 if (before && before->getType() == ltOFFER)
+
241 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
+
242
+
243 if (after && after->getType() == ltOFFER)
+
244 bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]);
+
245}
+
246
+
247bool
+
248NoBadOffers::finalize(
+
249 STTx const&,
+
250 TER const,
+
251 XRPAmount const,
+
252 ReadView const&,
+
253 beast::Journal const& j)
+
254{
+
255 if (bad_)
+
256 {
+
257 JLOG(j.fatal()) << "Invariant failed: offer with a bad amount";
+
258 return false;
+
259 }
+
260
+
261 return true;
+
262}
263
-
264void
-
265NoZeroEscrow::visitEntry(
-
266 bool isDelete,
-
267 std::shared_ptr<SLE const> const& before,
-
268 std::shared_ptr<SLE const> const& after)
-
269{
-
270 auto isBad = [](STAmount const& amount) {
-
271 if (!amount.native())
-
272 return true;
-
273
-
274 if (amount.xrp() <= XRPAmount{0})
-
275 return true;
-
276
-
277 if (amount.xrp() >= INITIAL_XRP)
-
278 return true;
-
279
-
280 return false;
-
281 };
-
282
-
283 if (before && before->getType() == ltESCROW)
-
284 bad_ |= isBad((*before)[sfAmount]);
-
285
-
286 if (after && after->getType() == ltESCROW)
-
287 bad_ |= isBad((*after)[sfAmount]);
-
288}
-
289
-
290bool
-
291NoZeroEscrow::finalize(
-
292 STTx const&,
-
293 TER const,
-
294 XRPAmount const,
-
295 ReadView const&,
-
296 beast::Journal const& j)
-
297{
-
298 if (bad_)
-
299 {
-
300 JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
-
301 return false;
-
302 }
-
303
-
304 return true;
-
305}
-
306
-
307//------------------------------------------------------------------------------
+
264//------------------------------------------------------------------------------
+
265
+
266void
+
267NoZeroEscrow::visitEntry(
+
268 bool isDelete,
+
269 std::shared_ptr<SLE const> const& before,
+
270 std::shared_ptr<SLE const> const& after)
+
271{
+
272 auto isBad = [](STAmount const& amount) {
+
273 if (!amount.native())
+
274 return true;
+
275
+
276 if (amount.xrp() <= XRPAmount{0})
+
277 return true;
+
278
+
279 if (amount.xrp() >= INITIAL_XRP)
+
280 return true;
+
281
+
282 return false;
+
283 };
+
284
+
285 if (before && before->getType() == ltESCROW)
+
286 bad_ |= isBad((*before)[sfAmount]);
+
287
+
288 if (after && after->getType() == ltESCROW)
+
289 bad_ |= isBad((*after)[sfAmount]);
+
290}
+
291
+
292bool
+
293NoZeroEscrow::finalize(
+
294 STTx const&,
+
295 TER const,
+
296 XRPAmount const,
+
297 ReadView const&,
+
298 beast::Journal const& j)
+
299{
+
300 if (bad_)
+
301 {
+
302 JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
+
303 return false;
+
304 }
+
305
+
306 return true;
+
307}
308
-
309void
-
310AccountRootsNotDeleted::visitEntry(
-
311 bool isDelete,
-
312 std::shared_ptr<SLE const> const& before,
-
313 std::shared_ptr<SLE const> const&)
-
314{
-
315 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
-
316 accountsDeleted_++;
-
317}
-
318
-
319bool
-
320AccountRootsNotDeleted::finalize(
-
321 STTx const& tx,
-
322 TER const result,
-
323 XRPAmount const,
-
324 ReadView const&,
-
325 beast::Journal const& j)
-
326{
-
327 // AMM account root can be deleted as the result of AMM withdraw/delete
-
328 // transaction when the total AMM LP Tokens balance goes to 0.
-
329 // A successful AccountDelete or AMMDelete MUST delete exactly
-
330 // one account root.
-
331 if ((tx.getTxnType() == ttACCOUNT_DELETE ||
-
332 tx.getTxnType() == ttAMM_DELETE ||
-
333 tx.getTxnType() == ttVAULT_DELETE) &&
-
334 result == tesSUCCESS)
-
335 {
-
336 if (accountsDeleted_ == 1)
-
337 return true;
-
338
-
339 if (accountsDeleted_ == 0)
-
340 JLOG(j.fatal()) << "Invariant failed: account deletion "
-
341 "succeeded without deleting an account";
-
342 else
-
343 JLOG(j.fatal()) << "Invariant failed: account deletion "
-
344 "succeeded but deleted multiple accounts!";
-
345 return false;
-
346 }
-
347
-
348 // A successful AMMWithdraw/AMMClawback MAY delete one account root
-
349 // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw
-
350 // deletes the AMM account, accountsDeleted_ is set if it is deleted.
-
351 if ((tx.getTxnType() == ttAMM_WITHDRAW ||
-
352 tx.getTxnType() == ttAMM_CLAWBACK) &&
-
353 result == tesSUCCESS && accountsDeleted_ == 1)
-
354 return true;
-
355
-
356 if (accountsDeleted_ == 0)
-
357 return true;
-
358
-
359 JLOG(j.fatal()) << "Invariant failed: an account root was deleted";
-
360 return false;
-
361}
-
362
-
363//------------------------------------------------------------------------------
+
309//------------------------------------------------------------------------------
+
310
+
311void
+
312AccountRootsNotDeleted::visitEntry(
+
313 bool isDelete,
+
314 std::shared_ptr<SLE const> const& before,
+
315 std::shared_ptr<SLE const> const&)
+
316{
+
317 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
+
318 accountsDeleted_++;
+
319}
+
320
+
321bool
+
322AccountRootsNotDeleted::finalize(
+
323 STTx const& tx,
+
324 TER const result,
+
325 XRPAmount const,
+
326 ReadView const&,
+
327 beast::Journal const& j)
+
328{
+
329 // AMM account root can be deleted as the result of AMM withdraw/delete
+
330 // transaction when the total AMM LP Tokens balance goes to 0.
+
331 // A successful AccountDelete or AMMDelete MUST delete exactly
+
332 // one account root.
+
333 if ((tx.getTxnType() == ttACCOUNT_DELETE ||
+
334 tx.getTxnType() == ttAMM_DELETE ||
+
335 tx.getTxnType() == ttVAULT_DELETE) &&
+
336 result == tesSUCCESS)
+
337 {
+
338 if (accountsDeleted_ == 1)
+
339 return true;
+
340
+
341 if (accountsDeleted_ == 0)
+
342 JLOG(j.fatal()) << "Invariant failed: account deletion "
+
343 "succeeded without deleting an account";
+
344 else
+
345 JLOG(j.fatal()) << "Invariant failed: account deletion "
+
346 "succeeded but deleted multiple accounts!";
+
347 return false;
+
348 }
+
349
+
350 // A successful AMMWithdraw/AMMClawback MAY delete one account root
+
351 // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw
+
352 // deletes the AMM account, accountsDeleted_ is set if it is deleted.
+
353 if ((tx.getTxnType() == ttAMM_WITHDRAW ||
+
354 tx.getTxnType() == ttAMM_CLAWBACK) &&
+
355 result == tesSUCCESS && accountsDeleted_ == 1)
+
356 return true;
+
357
+
358 if (accountsDeleted_ == 0)
+
359 return true;
+
360
+
361 JLOG(j.fatal()) << "Invariant failed: an account root was deleted";
+
362 return false;
+
363}
364
-
365void
-
366AccountRootsDeletedClean::visitEntry(
-
367 bool isDelete,
-
368 std::shared_ptr<SLE const> const& before,
-
369 std::shared_ptr<SLE const> const&)
-
370{
-
371 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
-
372 accountsDeleted_.emplace_back(before);
-
373}
-
374
-
375bool
-
376AccountRootsDeletedClean::finalize(
-
377 STTx const& tx,
-
378 TER const result,
-
379 XRPAmount const,
-
380 ReadView const& view,
-
381 beast::Journal const& j)
-
382{
-
383 // Always check for objects in the ledger, but to prevent differing
-
384 // transaction processing results, however unlikely, only fail if the
-
385 // feature is enabled. Enabled, or not, though, a fatal-level message will
-
386 // be logged
-
387 [[maybe_unused]] bool const enforce =
-
388 view.rules().enabled(featureInvariantsV1_1);
-
389
-
390 auto const objectExists = [&view, enforce, &j](auto const& keylet) {
-
391 (void)enforce;
-
392 if (auto const sle = view.read(keylet))
-
393 {
-
394 // Finding the object is bad
-
395 auto const typeName = [&sle]() {
-
396 auto item =
-
397 LedgerFormats::getInstance().findByType(sle->getType());
-
398
-
399 if (item != nullptr)
-
400 return item->getName();
-
401 return std::to_string(sle->getType());
-
402 }();
-
403
-
404 JLOG(j.fatal())
-
405 << "Invariant failed: account deletion left behind a "
-
406 << typeName << " object";
-
407 XRPL_ASSERT(
-
408 enforce,
-
409 "ripple::AccountRootsDeletedClean::finalize::objectExists : "
-
410 "account deletion left no objects behind");
-
411 return true;
-
412 }
-
413 return false;
-
414 };
-
415
-
416 for (auto const& accountSLE : accountsDeleted_)
-
417 {
-
418 auto const accountID = accountSLE->getAccountID(sfAccount);
-
419 // Simple types
-
420 for (auto const& [keyletfunc, _, __] : directAccountKeylets)
-
421 {
-
422 if (objectExists(std::invoke(keyletfunc, accountID)) && enforce)
-
423 return false;
-
424 }
-
425
-
426 {
-
427 // NFT pages. ntfpage_min and nftpage_max were already explicitly
-
428 // checked above as entries in directAccountKeylets. This uses
-
429 // view.succ() to check for any NFT pages in between the two
-
430 // endpoints.
-
431 Keylet const first = keylet::nftpage_min(accountID);
-
432 Keylet const last = keylet::nftpage_max(accountID);
-
433
-
434 std::optional<uint256> key = view.succ(first.key, last.key.next());
+
365//------------------------------------------------------------------------------
+
366
+
367void
+
368AccountRootsDeletedClean::visitEntry(
+
369 bool isDelete,
+
370 std::shared_ptr<SLE const> const& before,
+
371 std::shared_ptr<SLE const> const&)
+
372{
+
373 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
+
374 accountsDeleted_.emplace_back(before);
+
375}
+
376
+
377bool
+
378AccountRootsDeletedClean::finalize(
+
379 STTx const& tx,
+
380 TER const result,
+
381 XRPAmount const,
+
382 ReadView const& view,
+
383 beast::Journal const& j)
+
384{
+
385 // Always check for objects in the ledger, but to prevent differing
+
386 // transaction processing results, however unlikely, only fail if the
+
387 // feature is enabled. Enabled, or not, though, a fatal-level message will
+
388 // be logged
+
389 [[maybe_unused]] bool const enforce =
+
390 view.rules().enabled(featureInvariantsV1_1);
+
391
+
392 auto const objectExists = [&view, enforce, &j](auto const& keylet) {
+
393 (void)enforce;
+
394 if (auto const sle = view.read(keylet))
+
395 {
+
396 // Finding the object is bad
+
397 auto const typeName = [&sle]() {
+
398 auto item =
+
399 LedgerFormats::getInstance().findByType(sle->getType());
+
400
+
401 if (item != nullptr)
+
402 return item->getName();
+
403 return std::to_string(sle->getType());
+
404 }();
+
405
+
406 JLOG(j.fatal())
+
407 << "Invariant failed: account deletion left behind a "
+
408 << typeName << " object";
+
409 XRPL_ASSERT(
+
410 enforce,
+
411 "ripple::AccountRootsDeletedClean::finalize::objectExists : "
+
412 "account deletion left no objects behind");
+
413 return true;
+
414 }
+
415 return false;
+
416 };
+
417
+
418 for (auto const& accountSLE : accountsDeleted_)
+
419 {
+
420 auto const accountID = accountSLE->getAccountID(sfAccount);
+
421 // Simple types
+
422 for (auto const& [keyletfunc, _, __] : directAccountKeylets)
+
423 {
+
424 if (objectExists(std::invoke(keyletfunc, accountID)) && enforce)
+
425 return false;
+
426 }
+
427
+
428 {
+
429 // NFT pages. ntfpage_min and nftpage_max were already explicitly
+
430 // checked above as entries in directAccountKeylets. This uses
+
431 // view.succ() to check for any NFT pages in between the two
+
432 // endpoints.
+
433 Keylet const first = keylet::nftpage_min(accountID);
+
434 Keylet const last = keylet::nftpage_max(accountID);
435
-
436 // current page
-
437 if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
-
438 return false;
-
439 }
-
440
-
441 // Keys directly stored in the AccountRoot object
-
442 if (auto const ammKey = accountSLE->at(~sfAMMID))
-
443 {
-
444 if (objectExists(keylet::amm(*ammKey)) && enforce)
-
445 return false;
-
446 }
-
447 }
-
448
-
449 return true;
-
450}
-
451
-
452//------------------------------------------------------------------------------
+
436 std::optional<uint256> key = view.succ(first.key, last.key.next());
+
437
+
438 // current page
+
439 if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
+
440 return false;
+
441 }
+
442
+
443 // Keys directly stored in the AccountRoot object
+
444 if (auto const ammKey = accountSLE->at(~sfAMMID))
+
445 {
+
446 if (objectExists(keylet::amm(*ammKey)) && enforce)
+
447 return false;
+
448 }
+
449 }
+
450
+
451 return true;
+
452}
453
-
454void
-
455LedgerEntryTypesMatch::visitEntry(
-
456 bool,
-
457 std::shared_ptr<SLE const> const& before,
-
458 std::shared_ptr<SLE const> const& after)
-
459{
-
460 if (before && after && before->getType() != after->getType())
-
461 typeMismatch_ = true;
-
462
-
463 if (after)
-
464 {
-
465 switch (after->getType())
-
466 {
-
467 case ltACCOUNT_ROOT:
-
468 case ltDELEGATE:
-
469 case ltDIR_NODE:
-
470 case ltRIPPLE_STATE:
-
471 case ltTICKET:
-
472 case ltSIGNER_LIST:
-
473 case ltOFFER:
-
474 case ltLEDGER_HASHES:
-
475 case ltAMENDMENTS:
-
476 case ltFEE_SETTINGS:
-
477 case ltESCROW:
-
478 case ltPAYCHAN:
-
479 case ltCHECK:
-
480 case ltDEPOSIT_PREAUTH:
-
481 case ltNEGATIVE_UNL:
-
482 case ltNFTOKEN_PAGE:
-
483 case ltNFTOKEN_OFFER:
-
484 case ltAMM:
-
485 case ltBRIDGE:
-
486 case ltXCHAIN_OWNED_CLAIM_ID:
-
487 case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID:
-
488 case ltDID:
-
489 case ltORACLE:
-
490 case ltMPTOKEN_ISSUANCE:
-
491 case ltMPTOKEN:
-
492 case ltCREDENTIAL:
-
493 case ltPERMISSIONED_DOMAIN:
-
494 case ltVAULT:
-
495 break;
-
496 default:
-
497 invalidTypeAdded_ = true;
-
498 break;
-
499 }
-
500 }
-
501}
-
502
-
503bool
-
504LedgerEntryTypesMatch::finalize(
-
505 STTx const&,
-
506 TER const,
-
507 XRPAmount const,
-
508 ReadView const&,
-
509 beast::Journal const& j)
-
510{
-
511 if ((!typeMismatch_) && (!invalidTypeAdded_))
-
512 return true;
-
513
-
514 if (typeMismatch_)
-
515 {
-
516 JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch";
-
517 }
-
518
-
519 if (invalidTypeAdded_)
-
520 {
-
521 JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added";
-
522 }
-
523
-
524 return false;
-
525}
-
526
-
527//------------------------------------------------------------------------------
+
454//------------------------------------------------------------------------------
+
455
+
456void
+
457LedgerEntryTypesMatch::visitEntry(
+
458 bool,
+
459 std::shared_ptr<SLE const> const& before,
+
460 std::shared_ptr<SLE const> const& after)
+
461{
+
462 if (before && after && before->getType() != after->getType())
+
463 typeMismatch_ = true;
+
464
+
465 if (after)
+
466 {
+
467 switch (after->getType())
+
468 {
+
469 case ltACCOUNT_ROOT:
+
470 case ltDELEGATE:
+
471 case ltDIR_NODE:
+
472 case ltRIPPLE_STATE:
+
473 case ltTICKET:
+
474 case ltSIGNER_LIST:
+
475 case ltOFFER:
+
476 case ltLEDGER_HASHES:
+
477 case ltAMENDMENTS:
+
478 case ltFEE_SETTINGS:
+
479 case ltESCROW:
+
480 case ltPAYCHAN:
+
481 case ltCHECK:
+
482 case ltDEPOSIT_PREAUTH:
+
483 case ltNEGATIVE_UNL:
+
484 case ltNFTOKEN_PAGE:
+
485 case ltNFTOKEN_OFFER:
+
486 case ltAMM:
+
487 case ltBRIDGE:
+
488 case ltXCHAIN_OWNED_CLAIM_ID:
+
489 case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID:
+
490 case ltDID:
+
491 case ltORACLE:
+
492 case ltMPTOKEN_ISSUANCE:
+
493 case ltMPTOKEN:
+
494 case ltCREDENTIAL:
+
495 case ltPERMISSIONED_DOMAIN:
+
496 case ltVAULT:
+
497 break;
+
498 default:
+
499 invalidTypeAdded_ = true;
+
500 break;
+
501 }
+
502 }
+
503}
+
504
+
505bool
+
506LedgerEntryTypesMatch::finalize(
+
507 STTx const&,
+
508 TER const,
+
509 XRPAmount const,
+
510 ReadView const&,
+
511 beast::Journal const& j)
+
512{
+
513 if ((!typeMismatch_) && (!invalidTypeAdded_))
+
514 return true;
+
515
+
516 if (typeMismatch_)
+
517 {
+
518 JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch";
+
519 }
+
520
+
521 if (invalidTypeAdded_)
+
522 {
+
523 JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added";
+
524 }
+
525
+
526 return false;
+
527}
528
-
529void
-
530NoXRPTrustLines::visitEntry(
-
531 bool,
-
532 std::shared_ptr<SLE const> const&,
-
533 std::shared_ptr<SLE const> const& after)
-
534{
-
535 if (after && after->getType() == ltRIPPLE_STATE)
-
536 {
-
537 // checking the issue directly here instead of
-
538 // relying on .native() just in case native somehow
-
539 // were systematically incorrect
-
540 xrpTrustLine_ =
-
541 after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
-
542 after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
-
543 }
-
544}
-
545
-
546bool
-
547NoXRPTrustLines::finalize(
-
548 STTx const&,
-
549 TER const,
-
550 XRPAmount const,
-
551 ReadView const&,
-
552 beast::Journal const& j)
-
553{
-
554 if (!xrpTrustLine_)
-
555 return true;
-
556
-
557 JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created";
-
558 return false;
-
559}
-
560
-
561//------------------------------------------------------------------------------
+
529//------------------------------------------------------------------------------
+
530
+
531void
+
532NoXRPTrustLines::visitEntry(
+
533 bool,
+
534 std::shared_ptr<SLE const> const&,
+
535 std::shared_ptr<SLE const> const& after)
+
536{
+
537 if (after && after->getType() == ltRIPPLE_STATE)
+
538 {
+
539 // checking the issue directly here instead of
+
540 // relying on .native() just in case native somehow
+
541 // were systematically incorrect
+
542 xrpTrustLine_ =
+
543 after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
+
544 after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
+
545 }
+
546}
+
547
+
548bool
+
549NoXRPTrustLines::finalize(
+
550 STTx const&,
+
551 TER const,
+
552 XRPAmount const,
+
553 ReadView const&,
+
554 beast::Journal const& j)
+
555{
+
556 if (!xrpTrustLine_)
+
557 return true;
+
558
+
559 JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created";
+
560 return false;
+
561}
562
-
563void
-
564NoDeepFreezeTrustLinesWithoutFreeze::visitEntry(
-
565 bool,
-
566 std::shared_ptr<SLE const> const&,
-
567 std::shared_ptr<SLE const> const& after)
-
568{
-
569 if (after && after->getType() == ltRIPPLE_STATE)
-
570 {
-
571 std::uint32_t const uFlags = after->getFieldU32(sfFlags);
-
572 bool const lowFreeze = uFlags & lsfLowFreeze;
-
573 bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze;
-
574
-
575 bool const highFreeze = uFlags & lsfHighFreeze;
-
576 bool const highDeepFreeze = uFlags & lsfHighDeepFreeze;
-
577
-
578 deepFreezeWithoutFreeze_ =
-
579 (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
-
580 }
-
581}
-
582
-
583bool
-
584NoDeepFreezeTrustLinesWithoutFreeze::finalize(
-
585 STTx const&,
-
586 TER const,
-
587 XRPAmount const,
-
588 ReadView const&,
-
589 beast::Journal const& j)
-
590{
-
591 if (!deepFreezeWithoutFreeze_)
-
592 return true;
-
593
-
594 JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag "
-
595 "without normal freeze was created";
-
596 return false;
-
597}
-
598
-
599//------------------------------------------------------------------------------
+
563//------------------------------------------------------------------------------
+
564
+
565void
+
566NoDeepFreezeTrustLinesWithoutFreeze::visitEntry(
+
567 bool,
+
568 std::shared_ptr<SLE const> const&,
+
569 std::shared_ptr<SLE const> const& after)
+
570{
+
571 if (after && after->getType() == ltRIPPLE_STATE)
+
572 {
+
573 std::uint32_t const uFlags = after->getFieldU32(sfFlags);
+
574 bool const lowFreeze = uFlags & lsfLowFreeze;
+
575 bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze;
+
576
+
577 bool const highFreeze = uFlags & lsfHighFreeze;
+
578 bool const highDeepFreeze = uFlags & lsfHighDeepFreeze;
+
579
+
580 deepFreezeWithoutFreeze_ =
+
581 (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
+
582 }
+
583}
+
584
+
585bool
+
586NoDeepFreezeTrustLinesWithoutFreeze::finalize(
+
587 STTx const&,
+
588 TER const,
+
589 XRPAmount const,
+
590 ReadView const&,
+
591 beast::Journal const& j)
+
592{
+
593 if (!deepFreezeWithoutFreeze_)
+
594 return true;
+
595
+
596 JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag "
+
597 "without normal freeze was created";
+
598 return false;
+
599}
600
-
601void
-
602TransfersNotFrozen::visitEntry(
-
603 bool isDelete,
-
604 std::shared_ptr<SLE const> const& before,
-
605 std::shared_ptr<SLE const> const& after)
-
606{
-
607 /*
-
608 * A trust line freeze state alone doesn't determine if a transfer is
-
609 * frozen. The transfer must be examined "end-to-end" because both sides of
-
610 * the transfer may have different freeze states and freeze impact depends
-
611 * on the transfer direction. This is why first we need to track the
-
612 * transfers using IssuerChanges senders/receivers.
-
613 *
-
614 * Only in validateIssuerChanges, after we collected all changes can we
-
615 * determine if the transfer is valid.
-
616 */
-
617 if (!isValidEntry(before, after))
-
618 {
-
619 return;
-
620 }
-
621
-
622 auto const balanceChange = calculateBalanceChange(before, after, isDelete);
-
623 if (balanceChange.signum() == 0)
-
624 {
-
625 return;
-
626 }
-
627
-
628 recordBalanceChanges(after, balanceChange);
-
629}
-
630
-
631bool
-
632TransfersNotFrozen::finalize(
-
633 STTx const& tx,
-
634 TER const ter,
-
635 XRPAmount const fee,
-
636 ReadView const& view,
-
637 beast::Journal const& j)
-
638{
-
639 /*
-
640 * We check this invariant regardless of deep freeze amendment status,
-
641 * allowing for detection and logging of potential issues even when the
-
642 * amendment is disabled.
-
643 *
-
644 * If an exploit that allows moving frozen assets is discovered,
-
645 * we can alert operators who monitor fatal messages and trigger assert in
-
646 * debug builds for an early warning.
-
647 *
-
648 * In an unlikely event that an exploit is found, this early detection
-
649 * enables encouraging the UNL to expedite deep freeze amendment activation
-
650 * or deploy hotfixes via new amendments. In case of a new amendment, we'd
-
651 * only have to change this line setting 'enforce' variable.
-
652 * enforce = view.rules().enabled(featureDeepFreeze) ||
-
653 * view.rules().enabled(fixFreezeExploit);
-
654 */
-
655 [[maybe_unused]] bool const enforce =
-
656 view.rules().enabled(featureDeepFreeze);
-
657
-
658 for (auto const& [issue, changes] : balanceChanges_)
-
659 {
-
660 auto const issuerSle = findIssuer(issue.account, view);
-
661 // It should be impossible for the issuer to not be found, but check
-
662 // just in case so rippled doesn't crash in release.
-
663 if (!issuerSle)
-
664 {
-
665 XRPL_ASSERT(
-
666 enforce,
-
667 "ripple::TransfersNotFrozen::finalize : enforce "
-
668 "invariant.");
-
669 if (enforce)
-
670 {
-
671 return false;
-
672 }
-
673 continue;
-
674 }
-
675
-
676 if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce))
-
677 {
-
678 return false;
-
679 }
-
680 }
-
681
-
682 return true;
-
683}
-
684
-
685bool
-
686TransfersNotFrozen::isValidEntry(
-
687 std::shared_ptr<SLE const> const& before,
-
688 std::shared_ptr<SLE const> const& after)
-
689{
-
690 // `after` can never be null, even if the trust line is deleted.
-
691 XRPL_ASSERT(
-
692 after, "ripple::TransfersNotFrozen::isValidEntry : valid after.");
-
693 if (!after)
-
694 {
-
695 return false;
-
696 }
-
697
-
698 if (after->getType() == ltACCOUNT_ROOT)
-
699 {
-
700 possibleIssuers_.emplace(after->at(sfAccount), after);
-
701 return false;
-
702 }
-
703
-
704 /* While LedgerEntryTypesMatch invariant also checks types, all invariants
-
705 * are processed regardless of previous failures.
-
706 *
-
707 * This type check is still necessary here because it prevents potential
-
708 * issues in subsequent processing.
-
709 */
-
710 return after->getType() == ltRIPPLE_STATE &&
-
711 (!before || before->getType() == ltRIPPLE_STATE);
-
712}
-
713
-
714STAmount
-
715TransfersNotFrozen::calculateBalanceChange(
-
716 std::shared_ptr<SLE const> const& before,
-
717 std::shared_ptr<SLE const> const& after,
-
718 bool isDelete)
-
719{
-
720 auto const getBalance = [](auto const& line, auto const& other, bool zero) {
-
721 STAmount amt =
-
722 line ? line->at(sfBalance) : other->at(sfBalance).zeroed();
-
723 return zero ? amt.zeroed() : amt;
-
724 };
-
725
-
726 /* Trust lines can be created dynamically by other transactions such as
-
727 * Payment and OfferCreate that cross offers. Such trust line won't be
-
728 * created frozen, but the sender might be, so the starting balance must be
-
729 * treated as zero.
-
730 */
-
731 auto const balanceBefore = getBalance(before, after, false);
-
732
-
733 /* Same as above, trust lines can be dynamically deleted, and for frozen
-
734 * trust lines, payments not involving the issuer must be blocked. This is
-
735 * achieved by treating the final balance as zero when isDelete=true to
-
736 * ensure frozen line restrictions are enforced even during deletion.
-
737 */
-
738 auto const balanceAfter = getBalance(after, before, isDelete);
-
739
-
740 return balanceAfter - balanceBefore;
-
741}
-
742
-
743void
-
744TransfersNotFrozen::recordBalance(Issue const& issue, BalanceChange change)
-
745{
-
746 XRPL_ASSERT(
-
747 change.balanceChangeSign,
-
748 "ripple::TransfersNotFrozen::recordBalance : valid trustline "
-
749 "balance sign.");
-
750 auto& changes = balanceChanges_[issue];
-
751 if (change.balanceChangeSign < 0)
-
752 changes.senders.emplace_back(std::move(change));
-
753 else
-
754 changes.receivers.emplace_back(std::move(change));
-
755}
-
756
-
757void
-
758TransfersNotFrozen::recordBalanceChanges(
-
759 std::shared_ptr<SLE const> const& after,
-
760 STAmount const& balanceChange)
-
761{
-
762 auto const balanceChangeSign = balanceChange.signum();
-
763 auto const currency = after->at(sfBalance).getCurrency();
-
764
-
765 // Change from low account's perspective, which is trust line default
-
766 recordBalance(
-
767 {currency, after->at(sfHighLimit).getIssuer()},
-
768 {after, balanceChangeSign});
-
769
-
770 // Change from high account's perspective, which reverses the sign.
-
771 recordBalance(
-
772 {currency, after->at(sfLowLimit).getIssuer()},
-
773 {after, -balanceChangeSign});
-
774}
-
775
-
776std::shared_ptr<SLE const>
-
777TransfersNotFrozen::findIssuer(AccountID const& issuerID, ReadView const& view)
-
778{
-
779 if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end())
-
780 {
-
781 return it->second;
-
782 }
-
783
-
784 return view.read(keylet::account(issuerID));
-
785}
-
786
-
787bool
-
788TransfersNotFrozen::validateIssuerChanges(
-
789 std::shared_ptr<SLE const> const& issuer,
-
790 IssuerChanges const& changes,
-
791 STTx const& tx,
-
792 beast::Journal const& j,
-
793 bool enforce)
-
794{
-
795 if (!issuer)
-
796 {
-
797 return false;
-
798 }
-
799
-
800 bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze);
-
801 if (changes.receivers.empty() || changes.senders.empty())
-
802 {
-
803 /* If there are no receivers, then the holder(s) are returning
-
804 * their tokens to the issuer. Likewise, if there are no
-
805 * senders, then the issuer is issuing tokens to the holder(s).
-
806 * This is allowed regardless of the issuer's freeze flags. (The
-
807 * holder may have contradicting freeze flags, but that will be
-
808 * checked when the holder is treated as issuer.)
-
809 */
-
810 return true;
-
811 }
-
812
-
813 for (auto const& actors : {changes.senders, changes.receivers})
-
814 {
-
815 for (auto const& change : actors)
-
816 {
-
817 bool const high = change.line->at(sfLowLimit).getIssuer() ==
-
818 issuer->at(sfAccount);
-
819
-
820 if (!validateFrozenState(
-
821 change, high, tx, j, enforce, globalFreeze))
-
822 {
-
823 return false;
-
824 }
-
825 }
-
826 }
-
827 return true;
-
828}
-
829
-
830bool
-
831TransfersNotFrozen::validateFrozenState(
-
832 BalanceChange const& change,
-
833 bool high,
-
834 STTx const& tx,
-
835 beast::Journal const& j,
-
836 bool enforce,
-
837 bool globalFreeze)
-
838{
-
839 bool const freeze = change.balanceChangeSign < 0 &&
-
840 change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze);
-
841 bool const deepFreeze =
-
842 change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze);
-
843 bool const frozen = globalFreeze || deepFreeze || freeze;
-
844
-
845 bool const isAMMLine = change.line->isFlag(lsfAMMNode);
+
601//------------------------------------------------------------------------------
+
602
+
603void
+
604TransfersNotFrozen::visitEntry(
+
605 bool isDelete,
+
606 std::shared_ptr<SLE const> const& before,
+
607 std::shared_ptr<SLE const> const& after)
+
608{
+
609 /*
+
610 * A trust line freeze state alone doesn't determine if a transfer is
+
611 * frozen. The transfer must be examined "end-to-end" because both sides of
+
612 * the transfer may have different freeze states and freeze impact depends
+
613 * on the transfer direction. This is why first we need to track the
+
614 * transfers using IssuerChanges senders/receivers.
+
615 *
+
616 * Only in validateIssuerChanges, after we collected all changes can we
+
617 * determine if the transfer is valid.
+
618 */
+
619 if (!isValidEntry(before, after))
+
620 {
+
621 return;
+
622 }
+
623
+
624 auto const balanceChange = calculateBalanceChange(before, after, isDelete);
+
625 if (balanceChange.signum() == 0)
+
626 {
+
627 return;
+
628 }
+
629
+
630 recordBalanceChanges(after, balanceChange);
+
631}
+
632
+
633bool
+
634TransfersNotFrozen::finalize(
+
635 STTx const& tx,
+
636 TER const ter,
+
637 XRPAmount const fee,
+
638 ReadView const& view,
+
639 beast::Journal const& j)
+
640{
+
641 /*
+
642 * We check this invariant regardless of deep freeze amendment status,
+
643 * allowing for detection and logging of potential issues even when the
+
644 * amendment is disabled.
+
645 *
+
646 * If an exploit that allows moving frozen assets is discovered,
+
647 * we can alert operators who monitor fatal messages and trigger assert in
+
648 * debug builds for an early warning.
+
649 *
+
650 * In an unlikely event that an exploit is found, this early detection
+
651 * enables encouraging the UNL to expedite deep freeze amendment activation
+
652 * or deploy hotfixes via new amendments. In case of a new amendment, we'd
+
653 * only have to change this line setting 'enforce' variable.
+
654 * enforce = view.rules().enabled(featureDeepFreeze) ||
+
655 * view.rules().enabled(fixFreezeExploit);
+
656 */
+
657 [[maybe_unused]] bool const enforce =
+
658 view.rules().enabled(featureDeepFreeze);
+
659
+
660 for (auto const& [issue, changes] : balanceChanges_)
+
661 {
+
662 auto const issuerSle = findIssuer(issue.account, view);
+
663 // It should be impossible for the issuer to not be found, but check
+
664 // just in case so rippled doesn't crash in release.
+
665 if (!issuerSle)
+
666 {
+
667 XRPL_ASSERT(
+
668 enforce,
+
669 "ripple::TransfersNotFrozen::finalize : enforce "
+
670 "invariant.");
+
671 if (enforce)
+
672 {
+
673 return false;
+
674 }
+
675 continue;
+
676 }
+
677
+
678 if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce))
+
679 {
+
680 return false;
+
681 }
+
682 }
+
683
+
684 return true;
+
685}
+
686
+
687bool
+
688TransfersNotFrozen::isValidEntry(
+
689 std::shared_ptr<SLE const> const& before,
+
690 std::shared_ptr<SLE const> const& after)
+
691{
+
692 // `after` can never be null, even if the trust line is deleted.
+
693 XRPL_ASSERT(
+
694 after, "ripple::TransfersNotFrozen::isValidEntry : valid after.");
+
695 if (!after)
+
696 {
+
697 return false;
+
698 }
+
699
+
700 if (after->getType() == ltACCOUNT_ROOT)
+
701 {
+
702 possibleIssuers_.emplace(after->at(sfAccount), after);
+
703 return false;
+
704 }
+
705
+
706 /* While LedgerEntryTypesMatch invariant also checks types, all invariants
+
707 * are processed regardless of previous failures.
+
708 *
+
709 * This type check is still necessary here because it prevents potential
+
710 * issues in subsequent processing.
+
711 */
+
712 return after->getType() == ltRIPPLE_STATE &&
+
713 (!before || before->getType() == ltRIPPLE_STATE);
+
714}
+
715
+
716STAmount
+
717TransfersNotFrozen::calculateBalanceChange(
+
718 std::shared_ptr<SLE const> const& before,
+
719 std::shared_ptr<SLE const> const& after,
+
720 bool isDelete)
+
721{
+
722 auto const getBalance = [](auto const& line, auto const& other, bool zero) {
+
723 STAmount amt =
+
724 line ? line->at(sfBalance) : other->at(sfBalance).zeroed();
+
725 return zero ? amt.zeroed() : amt;
+
726 };
+
727
+
728 /* Trust lines can be created dynamically by other transactions such as
+
729 * Payment and OfferCreate that cross offers. Such trust line won't be
+
730 * created frozen, but the sender might be, so the starting balance must be
+
731 * treated as zero.
+
732 */
+
733 auto const balanceBefore = getBalance(before, after, false);
+
734
+
735 /* Same as above, trust lines can be dynamically deleted, and for frozen
+
736 * trust lines, payments not involving the issuer must be blocked. This is
+
737 * achieved by treating the final balance as zero when isDelete=true to
+
738 * ensure frozen line restrictions are enforced even during deletion.
+
739 */
+
740 auto const balanceAfter = getBalance(after, before, isDelete);
+
741
+
742 return balanceAfter - balanceBefore;
+
743}
+
744
+
745void
+
746TransfersNotFrozen::recordBalance(Issue const& issue, BalanceChange change)
+
747{
+
748 XRPL_ASSERT(
+
749 change.balanceChangeSign,
+
750 "ripple::TransfersNotFrozen::recordBalance : valid trustline "
+
751 "balance sign.");
+
752 auto& changes = balanceChanges_[issue];
+
753 if (change.balanceChangeSign < 0)
+
754 changes.senders.emplace_back(std::move(change));
+
755 else
+
756 changes.receivers.emplace_back(std::move(change));
+
757}
+
758
+
759void
+
760TransfersNotFrozen::recordBalanceChanges(
+
761 std::shared_ptr<SLE const> const& after,
+
762 STAmount const& balanceChange)
+
763{
+
764 auto const balanceChangeSign = balanceChange.signum();
+
765 auto const currency = after->at(sfBalance).getCurrency();
+
766
+
767 // Change from low account's perspective, which is trust line default
+
768 recordBalance(
+
769 {currency, after->at(sfHighLimit).getIssuer()},
+
770 {after, balanceChangeSign});
+
771
+
772 // Change from high account's perspective, which reverses the sign.
+
773 recordBalance(
+
774 {currency, after->at(sfLowLimit).getIssuer()},
+
775 {after, -balanceChangeSign});
+
776}
+
777
+
778std::shared_ptr<SLE const>
+
779TransfersNotFrozen::findIssuer(AccountID const& issuerID, ReadView const& view)
+
780{
+
781 if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end())
+
782 {
+
783 return it->second;
+
784 }
+
785
+
786 return view.read(keylet::account(issuerID));
+
787}
+
788
+
789bool
+
790TransfersNotFrozen::validateIssuerChanges(
+
791 std::shared_ptr<SLE const> const& issuer,
+
792 IssuerChanges const& changes,
+
793 STTx const& tx,
+
794 beast::Journal const& j,
+
795 bool enforce)
+
796{
+
797 if (!issuer)
+
798 {
+
799 return false;
+
800 }
+
801
+
802 bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze);
+
803 if (changes.receivers.empty() || changes.senders.empty())
+
804 {
+
805 /* If there are no receivers, then the holder(s) are returning
+
806 * their tokens to the issuer. Likewise, if there are no
+
807 * senders, then the issuer is issuing tokens to the holder(s).
+
808 * This is allowed regardless of the issuer's freeze flags. (The
+
809 * holder may have contradicting freeze flags, but that will be
+
810 * checked when the holder is treated as issuer.)
+
811 */
+
812 return true;
+
813 }
+
814
+
815 for (auto const& actors : {changes.senders, changes.receivers})
+
816 {
+
817 for (auto const& change : actors)
+
818 {
+
819 bool const high = change.line->at(sfLowLimit).getIssuer() ==
+
820 issuer->at(sfAccount);
+
821
+
822 if (!validateFrozenState(
+
823 change, high, tx, j, enforce, globalFreeze))
+
824 {
+
825 return false;
+
826 }
+
827 }
+
828 }
+
829 return true;
+
830}
+
831
+
832bool
+
833TransfersNotFrozen::validateFrozenState(
+
834 BalanceChange const& change,
+
835 bool high,
+
836 STTx const& tx,
+
837 beast::Journal const& j,
+
838 bool enforce,
+
839 bool globalFreeze)
+
840{
+
841 bool const freeze = change.balanceChangeSign < 0 &&
+
842 change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze);
+
843 bool const deepFreeze =
+
844 change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze);
+
845 bool const frozen = globalFreeze || deepFreeze || freeze;
846
-
847 if (!frozen)
-
848 {
-
849 return true;
-
850 }
-
851
-
852 // AMMClawbacks are allowed to override some freeze rules
-
853 if ((!isAMMLine || globalFreeze) && tx.getTxnType() == ttAMM_CLAWBACK)
-
854 {
-
855 JLOG(j.debug()) << "Invariant check allowing funds to be moved "
-
856 << (change.balanceChangeSign > 0 ? "to" : "from")
-
857 << " a frozen trustline for AMMClawback "
-
858 << tx.getTransactionID();
-
859 return true;
-
860 }
-
861
-
862 JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for "
-
863 << tx.getTransactionID();
-
864 XRPL_ASSERT(
-
865 enforce,
-
866 "ripple::TransfersNotFrozen::validateFrozenState : enforce "
-
867 "invariant.");
-
868
-
869 if (enforce)
-
870 {
-
871 return false;
-
872 }
-
873
-
874 return true;
-
875}
-
876
-
877//------------------------------------------------------------------------------
+
847 bool const isAMMLine = change.line->isFlag(lsfAMMNode);
+
848
+
849 if (!frozen)
+
850 {
+
851 return true;
+
852 }
+
853
+
854 // AMMClawbacks are allowed to override some freeze rules
+
855 if ((!isAMMLine || globalFreeze) && tx.getTxnType() == ttAMM_CLAWBACK)
+
856 {
+
857 JLOG(j.debug()) << "Invariant check allowing funds to be moved "
+
858 << (change.balanceChangeSign > 0 ? "to" : "from")
+
859 << " a frozen trustline for AMMClawback "
+
860 << tx.getTransactionID();
+
861 return true;
+
862 }
+
863
+
864 JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for "
+
865 << tx.getTransactionID();
+
866 XRPL_ASSERT(
+
867 enforce,
+
868 "ripple::TransfersNotFrozen::validateFrozenState : enforce "
+
869 "invariant.");
+
870
+
871 if (enforce)
+
872 {
+
873 return false;
+
874 }
+
875
+
876 return true;
+
877}
878
-
879void
-
880ValidNewAccountRoot::visitEntry(
-
881 bool,
-
882 std::shared_ptr<SLE const> const& before,
-
883 std::shared_ptr<SLE const> const& after)
-
884{
-
885 if (!before && after->getType() == ltACCOUNT_ROOT)
-
886 {
-
887 accountsCreated_++;
-
888 accountSeq_ = (*after)[sfSequence];
-
889 pseudoAccount_ = isPseudoAccount(after);
-
890 flags_ = after->getFlags();
-
891 }
-
892}
-
893
-
894bool
-
895ValidNewAccountRoot::finalize(
-
896 STTx const& tx,
-
897 TER const result,
-
898 XRPAmount const,
-
899 ReadView const& view,
-
900 beast::Journal const& j)
-
901{
-
902 if (accountsCreated_ == 0)
-
903 return true;
-
904
-
905 if (accountsCreated_ > 1)
-
906 {
-
907 JLOG(j.fatal()) << "Invariant failed: multiple accounts "
-
908 "created in a single transaction";
-
909 return false;
-
910 }
-
911
-
912 // From this point on we know exactly one account was created.
-
913 if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE ||
-
914 tx.getTxnType() == ttVAULT_CREATE ||
-
915 tx.getTxnType() == ttXCHAIN_ADD_CLAIM_ATTESTATION ||
-
916 tx.getTxnType() == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) &&
-
917 result == tesSUCCESS)
-
918 {
-
919 bool const pseudoAccount =
-
920 (pseudoAccount_ && view.rules().enabled(featureSingleAssetVault));
-
921
-
922 if (pseudoAccount && tx.getTxnType() != ttAMM_CREATE &&
-
923 tx.getTxnType() != ttVAULT_CREATE)
-
924 {
-
925 JLOG(j.fatal()) << "Invariant failed: pseudo-account created by a "
-
926 "wrong transaction type";
-
927 return false;
-
928 }
-
929
-
930 std::uint32_t const startingSeq = //
-
931 pseudoAccount //
-
932 ? 0 //
-
933 : view.rules().enabled(featureDeletableAccounts) //
-
934 ? view.seq() //
-
935 : 1;
-
936
-
937 if (accountSeq_ != startingSeq)
-
938 {
-
939 JLOG(j.fatal()) << "Invariant failed: account created with "
-
940 "wrong starting sequence number";
-
941 return false;
-
942 }
-
943
-
944 if (pseudoAccount)
-
945 {
-
946 std::uint32_t const expected =
-
947 (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
-
948 if (flags_ != expected)
-
949 {
-
950 JLOG(j.fatal())
-
951 << "Invariant failed: pseudo-account created with "
-
952 "wrong flags";
-
953 return false;
-
954 }
-
955 }
-
956
-
957 return true;
-
958 }
-
959
-
960 JLOG(j.fatal()) << "Invariant failed: account root created illegally";
-
961 return false;
-
962}
-
963
-
964//------------------------------------------------------------------------------
+
879//------------------------------------------------------------------------------
+
880
+
881void
+
882ValidNewAccountRoot::visitEntry(
+
883 bool,
+
884 std::shared_ptr<SLE const> const& before,
+
885 std::shared_ptr<SLE const> const& after)
+
886{
+
887 if (!before && after->getType() == ltACCOUNT_ROOT)
+
888 {
+
889 accountsCreated_++;
+
890 accountSeq_ = (*after)[sfSequence];
+
891 pseudoAccount_ = isPseudoAccount(after);
+
892 flags_ = after->getFlags();
+
893 }
+
894}
+
895
+
896bool
+
897ValidNewAccountRoot::finalize(
+
898 STTx const& tx,
+
899 TER const result,
+
900 XRPAmount const,
+
901 ReadView const& view,
+
902 beast::Journal const& j)
+
903{
+
904 if (accountsCreated_ == 0)
+
905 return true;
+
906
+
907 if (accountsCreated_ > 1)
+
908 {
+
909 JLOG(j.fatal()) << "Invariant failed: multiple accounts "
+
910 "created in a single transaction";
+
911 return false;
+
912 }
+
913
+
914 // From this point on we know exactly one account was created.
+
915 if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE ||
+
916 tx.getTxnType() == ttVAULT_CREATE ||
+
917 tx.getTxnType() == ttXCHAIN_ADD_CLAIM_ATTESTATION ||
+
918 tx.getTxnType() == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) &&
+
919 result == tesSUCCESS)
+
920 {
+
921 bool const pseudoAccount =
+
922 (pseudoAccount_ && view.rules().enabled(featureSingleAssetVault));
+
923
+
924 if (pseudoAccount && tx.getTxnType() != ttAMM_CREATE &&
+
925 tx.getTxnType() != ttVAULT_CREATE)
+
926 {
+
927 JLOG(j.fatal()) << "Invariant failed: pseudo-account created by a "
+
928 "wrong transaction type";
+
929 return false;
+
930 }
+
931
+
932 std::uint32_t const startingSeq = //
+
933 pseudoAccount //
+
934 ? 0 //
+
935 : view.rules().enabled(featureDeletableAccounts) //
+
936 ? view.seq() //
+
937 : 1;
+
938
+
939 if (accountSeq_ != startingSeq)
+
940 {
+
941 JLOG(j.fatal()) << "Invariant failed: account created with "
+
942 "wrong starting sequence number";
+
943 return false;
+
944 }
+
945
+
946 if (pseudoAccount)
+
947 {
+
948 std::uint32_t const expected =
+
949 (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
+
950 if (flags_ != expected)
+
951 {
+
952 JLOG(j.fatal())
+
953 << "Invariant failed: pseudo-account created with "
+
954 "wrong flags";
+
955 return false;
+
956 }
+
957 }
+
958
+
959 return true;
+
960 }
+
961
+
962 JLOG(j.fatal()) << "Invariant failed: account root created illegally";
+
963 return false;
+
964}
965
-
966void
-
967ValidNFTokenPage::visitEntry(
-
968 bool isDelete,
-
969 std::shared_ptr<SLE const> const& before,
-
970 std::shared_ptr<SLE const> const& after)
-
971{
-
972 static constexpr uint256 const& pageBits = nft::pageMask;
-
973 static constexpr uint256 const accountBits = ~pageBits;
-
974
-
975 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
-
976 (after && after->getType() != ltNFTOKEN_PAGE))
-
977 return;
-
978
-
979 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
-
980 uint256 const account = sle->key() & accountBits;
-
981 uint256 const hiLimit = sle->key() & pageBits;
-
982 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
-
983
-
984 // Make sure that any page links...
-
985 // 1. Are properly associated with the owning account and
-
986 // 2. The page is correctly ordered between links.
-
987 if (prev)
-
988 {
-
989 if (account != (*prev & accountBits))
-
990 badLink_ = true;
-
991
-
992 if (hiLimit <= (*prev & pageBits))
-
993 badLink_ = true;
-
994 }
-
995
-
996 if (auto const next = (*sle)[~sfNextPageMin])
-
997 {
-
998 if (account != (*next & accountBits))
-
999 badLink_ = true;
-
1000
-
1001 if (hiLimit >= (*next & pageBits))
-
1002 badLink_ = true;
-
1003 }
-
1004
-
1005 {
-
1006 auto const& nftokens = sle->getFieldArray(sfNFTokens);
-
1007
-
1008 // An NFTokenPage should never contain too many tokens or be empty.
-
1009 if (std::size_t const nftokenCount = nftokens.size();
-
1010 (!isDelete && nftokenCount == 0) ||
-
1011 nftokenCount > dirMaxTokensPerPage)
-
1012 invalidSize_ = true;
-
1013
-
1014 // If prev is valid, use it to establish a lower bound for
-
1015 // page entries. If prev is not valid the lower bound is zero.
-
1016 uint256 const loLimit =
-
1017 prev ? *prev & pageBits : uint256(beast::zero);
-
1018
-
1019 // Also verify that all NFTokenIDs in the page are sorted.
-
1020 uint256 loCmp = loLimit;
-
1021 for (auto const& obj : nftokens)
-
1022 {
-
1023 uint256 const tokenID = obj[sfNFTokenID];
-
1024 if (!nft::compareTokens(loCmp, tokenID))
-
1025 badSort_ = true;
-
1026 loCmp = tokenID;
-
1027
-
1028 // None of the NFTs on this page should belong on lower or
-
1029 // higher pages.
-
1030 if (uint256 const tokenPageBits = tokenID & pageBits;
-
1031 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
-
1032 badEntry_ = true;
-
1033
-
1034 if (auto uri = obj[~sfURI]; uri && uri->empty())
-
1035 badURI_ = true;
-
1036 }
-
1037 }
-
1038 };
-
1039
-
1040 if (before)
-
1041 {
-
1042 check(before);
-
1043
-
1044 // While an account's NFToken directory contains any NFTokens, the last
-
1045 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
-
1046 // never be deleted.
-
1047 if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
-
1048 before->isFieldPresent(sfPreviousPageMin))
-
1049 {
-
1050 deletedFinalPage_ = true;
-
1051 }
-
1052 }
-
1053
-
1054 if (after)
-
1055 check(after);
-
1056
-
1057 if (!isDelete && before && after)
-
1058 {
-
1059 // If the NFTokenPage
-
1060 // 1. Has a NextMinPage field in before, but loses it in after, and
-
1061 // 2. This is not the last page in the directory
-
1062 // Then we have identified a corruption in the links between the
-
1063 // NFToken pages in the NFToken directory.
-
1064 if ((before->key() & nft::pageMask) != nft::pageMask &&
-
1065 before->isFieldPresent(sfNextPageMin) &&
-
1066 !after->isFieldPresent(sfNextPageMin))
-
1067 {
-
1068 deletedLink_ = true;
-
1069 }
-
1070 }
-
1071}
-
1072
-
1073bool
-
1074ValidNFTokenPage::finalize(
-
1075 STTx const& tx,
-
1076 TER const result,
-
1077 XRPAmount const,
-
1078 ReadView const& view,
-
1079 beast::Journal const& j)
-
1080{
-
1081 if (badLink_)
-
1082 {
-
1083 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
-
1084 return false;
-
1085 }
-
1086
-
1087 if (badEntry_)
-
1088 {
-
1089 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
-
1090 return false;
-
1091 }
-
1092
-
1093 if (badSort_)
-
1094 {
-
1095 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
-
1096 return false;
-
1097 }
-
1098
-
1099 if (badURI_)
-
1100 {
-
1101 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
-
1102 return false;
-
1103 }
-
1104
-
1105 if (invalidSize_)
-
1106 {
-
1107 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
-
1108 return false;
-
1109 }
-
1110
-
1111 if (view.rules().enabled(fixNFTokenPageLinks))
-
1112 {
-
1113 if (deletedFinalPage_)
-
1114 {
-
1115 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
-
1116 "non-empty directory.";
-
1117 return false;
-
1118 }
-
1119 if (deletedLink_)
-
1120 {
-
1121 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
-
1122 return false;
-
1123 }
-
1124 }
-
1125
-
1126 return true;
-
1127}
-
1128
-
1129//------------------------------------------------------------------------------
-
1130void
-
1131NFTokenCountTracking::visitEntry(
-
1132 bool,
-
1133 std::shared_ptr<SLE const> const& before,
-
1134 std::shared_ptr<SLE const> const& after)
-
1135{
-
1136 if (before && before->getType() == ltACCOUNT_ROOT)
-
1137 {
-
1138 beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
-
1139 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
-
1140 }
-
1141
-
1142 if (after && after->getType() == ltACCOUNT_ROOT)
-
1143 {
-
1144 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
-
1145 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
-
1146 }
-
1147}
-
1148
-
1149bool
-
1150NFTokenCountTracking::finalize(
-
1151 STTx const& tx,
-
1152 TER const result,
-
1153 XRPAmount const,
-
1154 ReadView const& view,
-
1155 beast::Journal const& j)
-
1156{
-
1157 if (TxType const txType = tx.getTxnType();
-
1158 txType != ttNFTOKEN_MINT && txType != ttNFTOKEN_BURN)
-
1159 {
-
1160 if (beforeMintedTotal != afterMintedTotal)
-
1161 {
-
1162 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
-
1163 "changed without a mint transaction!";
-
1164 return false;
-
1165 }
-
1166
-
1167 if (beforeBurnedTotal != afterBurnedTotal)
-
1168 {
-
1169 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
-
1170 "changed without a burn transaction!";
-
1171 return false;
-
1172 }
-
1173
-
1174 return true;
-
1175 }
-
1176
-
1177 if (tx.getTxnType() == ttNFTOKEN_MINT)
-
1178 {
-
1179 if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
-
1180 {
-
1181 JLOG(j.fatal())
-
1182 << "Invariant failed: successful minting didn't increase "
-
1183 "the number of minted tokens.";
-
1184 return false;
-
1185 }
-
1186
-
1187 if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
-
1188 {
-
1189 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
-
1190 "number of minted tokens.";
-
1191 return false;
-
1192 }
-
1193
-
1194 if (beforeBurnedTotal != afterBurnedTotal)
-
1195 {
-
1196 JLOG(j.fatal())
-
1197 << "Invariant failed: minting changed the number of "
-
1198 "burned tokens.";
-
1199 return false;
-
1200 }
-
1201 }
-
1202
-
1203 if (tx.getTxnType() == ttNFTOKEN_BURN)
-
1204 {
-
1205 if (result == tesSUCCESS)
-
1206 {
-
1207 if (beforeBurnedTotal >= afterBurnedTotal)
-
1208 {
-
1209 JLOG(j.fatal())
-
1210 << "Invariant failed: successful burning didn't increase "
-
1211 "the number of burned tokens.";
-
1212 return false;
-
1213 }
-
1214 }
-
1215
-
1216 if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
-
1217 {
-
1218 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
-
1219 "number of burned tokens.";
-
1220 return false;
-
1221 }
-
1222
-
1223 if (beforeMintedTotal != afterMintedTotal)
-
1224 {
-
1225 JLOG(j.fatal())
-
1226 << "Invariant failed: burning changed the number of "
-
1227 "minted tokens.";
-
1228 return false;
-
1229 }
-
1230 }
-
1231
-
1232 return true;
-
1233}
-
1234
-
1235//------------------------------------------------------------------------------
+
966//------------------------------------------------------------------------------
+
967
+
968void
+
969ValidNFTokenPage::visitEntry(
+
970 bool isDelete,
+
971 std::shared_ptr<SLE const> const& before,
+
972 std::shared_ptr<SLE const> const& after)
+
973{
+
974 static constexpr uint256 const& pageBits = nft::pageMask;
+
975 static constexpr uint256 const accountBits = ~pageBits;
+
976
+
977 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
+
978 (after && after->getType() != ltNFTOKEN_PAGE))
+
979 return;
+
980
+
981 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
+
982 uint256 const account = sle->key() & accountBits;
+
983 uint256 const hiLimit = sle->key() & pageBits;
+
984 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
+
985
+
986 // Make sure that any page links...
+
987 // 1. Are properly associated with the owning account and
+
988 // 2. The page is correctly ordered between links.
+
989 if (prev)
+
990 {
+
991 if (account != (*prev & accountBits))
+
992 badLink_ = true;
+
993
+
994 if (hiLimit <= (*prev & pageBits))
+
995 badLink_ = true;
+
996 }
+
997
+
998 if (auto const next = (*sle)[~sfNextPageMin])
+
999 {
+
1000 if (account != (*next & accountBits))
+
1001 badLink_ = true;
+
1002
+
1003 if (hiLimit >= (*next & pageBits))
+
1004 badLink_ = true;
+
1005 }
+
1006
+
1007 {
+
1008 auto const& nftokens = sle->getFieldArray(sfNFTokens);
+
1009
+
1010 // An NFTokenPage should never contain too many tokens or be empty.
+
1011 if (std::size_t const nftokenCount = nftokens.size();
+
1012 (!isDelete && nftokenCount == 0) ||
+
1013 nftokenCount > dirMaxTokensPerPage)
+
1014 invalidSize_ = true;
+
1015
+
1016 // If prev is valid, use it to establish a lower bound for
+
1017 // page entries. If prev is not valid the lower bound is zero.
+
1018 uint256 const loLimit =
+
1019 prev ? *prev & pageBits : uint256(beast::zero);
+
1020
+
1021 // Also verify that all NFTokenIDs in the page are sorted.
+
1022 uint256 loCmp = loLimit;
+
1023 for (auto const& obj : nftokens)
+
1024 {
+
1025 uint256 const tokenID = obj[sfNFTokenID];
+
1026 if (!nft::compareTokens(loCmp, tokenID))
+
1027 badSort_ = true;
+
1028 loCmp = tokenID;
+
1029
+
1030 // None of the NFTs on this page should belong on lower or
+
1031 // higher pages.
+
1032 if (uint256 const tokenPageBits = tokenID & pageBits;
+
1033 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
+
1034 badEntry_ = true;
+
1035
+
1036 if (auto uri = obj[~sfURI]; uri && uri->empty())
+
1037 badURI_ = true;
+
1038 }
+
1039 }
+
1040 };
+
1041
+
1042 if (before)
+
1043 {
+
1044 check(before);
+
1045
+
1046 // While an account's NFToken directory contains any NFTokens, the last
+
1047 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
+
1048 // never be deleted.
+
1049 if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
+
1050 before->isFieldPresent(sfPreviousPageMin))
+
1051 {
+
1052 deletedFinalPage_ = true;
+
1053 }
+
1054 }
+
1055
+
1056 if (after)
+
1057 check(after);
+
1058
+
1059 if (!isDelete && before && after)
+
1060 {
+
1061 // If the NFTokenPage
+
1062 // 1. Has a NextMinPage field in before, but loses it in after, and
+
1063 // 2. This is not the last page in the directory
+
1064 // Then we have identified a corruption in the links between the
+
1065 // NFToken pages in the NFToken directory.
+
1066 if ((before->key() & nft::pageMask) != nft::pageMask &&
+
1067 before->isFieldPresent(sfNextPageMin) &&
+
1068 !after->isFieldPresent(sfNextPageMin))
+
1069 {
+
1070 deletedLink_ = true;
+
1071 }
+
1072 }
+
1073}
+
1074
+
1075bool
+
1076ValidNFTokenPage::finalize(
+
1077 STTx const& tx,
+
1078 TER const result,
+
1079 XRPAmount const,
+
1080 ReadView const& view,
+
1081 beast::Journal const& j)
+
1082{
+
1083 if (badLink_)
+
1084 {
+
1085 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
+
1086 return false;
+
1087 }
+
1088
+
1089 if (badEntry_)
+
1090 {
+
1091 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
+
1092 return false;
+
1093 }
+
1094
+
1095 if (badSort_)
+
1096 {
+
1097 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
+
1098 return false;
+
1099 }
+
1100
+
1101 if (badURI_)
+
1102 {
+
1103 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
+
1104 return false;
+
1105 }
+
1106
+
1107 if (invalidSize_)
+
1108 {
+
1109 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
+
1110 return false;
+
1111 }
+
1112
+
1113 if (view.rules().enabled(fixNFTokenPageLinks))
+
1114 {
+
1115 if (deletedFinalPage_)
+
1116 {
+
1117 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
+
1118 "non-empty directory.";
+
1119 return false;
+
1120 }
+
1121 if (deletedLink_)
+
1122 {
+
1123 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
+
1124 return false;
+
1125 }
+
1126 }
+
1127
+
1128 return true;
+
1129}
+
1130
+
1131//------------------------------------------------------------------------------
+
1132void
+
1133NFTokenCountTracking::visitEntry(
+
1134 bool,
+
1135 std::shared_ptr<SLE const> const& before,
+
1136 std::shared_ptr<SLE const> const& after)
+
1137{
+
1138 if (before && before->getType() == ltACCOUNT_ROOT)
+
1139 {
+
1140 beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
+
1141 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
+
1142 }
+
1143
+
1144 if (after && after->getType() == ltACCOUNT_ROOT)
+
1145 {
+
1146 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
+
1147 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
+
1148 }
+
1149}
+
1150
+
1151bool
+
1152NFTokenCountTracking::finalize(
+
1153 STTx const& tx,
+
1154 TER const result,
+
1155 XRPAmount const,
+
1156 ReadView const& view,
+
1157 beast::Journal const& j)
+
1158{
+
1159 if (TxType const txType = tx.getTxnType();
+
1160 txType != ttNFTOKEN_MINT && txType != ttNFTOKEN_BURN)
+
1161 {
+
1162 if (beforeMintedTotal != afterMintedTotal)
+
1163 {
+
1164 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
+
1165 "changed without a mint transaction!";
+
1166 return false;
+
1167 }
+
1168
+
1169 if (beforeBurnedTotal != afterBurnedTotal)
+
1170 {
+
1171 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
+
1172 "changed without a burn transaction!";
+
1173 return false;
+
1174 }
+
1175
+
1176 return true;
+
1177 }
+
1178
+
1179 if (tx.getTxnType() == ttNFTOKEN_MINT)
+
1180 {
+
1181 if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
+
1182 {
+
1183 JLOG(j.fatal())
+
1184 << "Invariant failed: successful minting didn't increase "
+
1185 "the number of minted tokens.";
+
1186 return false;
+
1187 }
+
1188
+
1189 if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
+
1190 {
+
1191 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
+
1192 "number of minted tokens.";
+
1193 return false;
+
1194 }
+
1195
+
1196 if (beforeBurnedTotal != afterBurnedTotal)
+
1197 {
+
1198 JLOG(j.fatal())
+
1199 << "Invariant failed: minting changed the number of "
+
1200 "burned tokens.";
+
1201 return false;
+
1202 }
+
1203 }
+
1204
+
1205 if (tx.getTxnType() == ttNFTOKEN_BURN)
+
1206 {
+
1207 if (result == tesSUCCESS)
+
1208 {
+
1209 if (beforeBurnedTotal >= afterBurnedTotal)
+
1210 {
+
1211 JLOG(j.fatal())
+
1212 << "Invariant failed: successful burning didn't increase "
+
1213 "the number of burned tokens.";
+
1214 return false;
+
1215 }
+
1216 }
+
1217
+
1218 if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
+
1219 {
+
1220 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
+
1221 "number of burned tokens.";
+
1222 return false;
+
1223 }
+
1224
+
1225 if (beforeMintedTotal != afterMintedTotal)
+
1226 {
+
1227 JLOG(j.fatal())
+
1228 << "Invariant failed: burning changed the number of "
+
1229 "minted tokens.";
+
1230 return false;
+
1231 }
+
1232 }
+
1233
+
1234 return true;
+
1235}
1236
-
1237void
-
1238ValidClawback::visitEntry(
-
1239 bool,
-
1240 std::shared_ptr<SLE const> const& before,
-
1241 std::shared_ptr<SLE const> const&)
-
1242{
-
1243 if (before && before->getType() == ltRIPPLE_STATE)
-
1244 trustlinesChanged++;
-
1245
-
1246 if (before && before->getType() == ltMPTOKEN)
-
1247 mptokensChanged++;
-
1248}
-
1249
-
1250bool
-
1251ValidClawback::finalize(
-
1252 STTx const& tx,
-
1253 TER const result,
-
1254 XRPAmount const,
-
1255 ReadView const& view,
-
1256 beast::Journal const& j)
-
1257{
-
1258 if (tx.getTxnType() != ttCLAWBACK)
-
1259 return true;
-
1260
-
1261 if (result == tesSUCCESS)
-
1262 {
-
1263 if (trustlinesChanged > 1)
-
1264 {
-
1265 JLOG(j.fatal())
-
1266 << "Invariant failed: more than one trustline changed.";
-
1267 return false;
-
1268 }
-
1269
-
1270 if (mptokensChanged > 1)
-
1271 {
-
1272 JLOG(j.fatal())
-
1273 << "Invariant failed: more than one mptokens changed.";
-
1274 return false;
-
1275 }
-
1276
-
1277 if (trustlinesChanged == 1)
-
1278 {
-
1279 AccountID const issuer = tx.getAccountID(sfAccount);
-
1280 STAmount const& amount = tx.getFieldAmount(sfAmount);
-
1281 AccountID const& holder = amount.getIssuer();
-
1282 STAmount const holderBalance = accountHolds(
-
1283 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
-
1284
-
1285 if (holderBalance.signum() < 0)
-
1286 {
-
1287 JLOG(j.fatal())
-
1288 << "Invariant failed: trustline balance is negative";
-
1289 return false;
-
1290 }
-
1291 }
-
1292 }
-
1293 else
-
1294 {
-
1295 if (trustlinesChanged != 0)
-
1296 {
-
1297 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
-
1298 "despite failure of the transaction.";
-
1299 return false;
-
1300 }
-
1301
-
1302 if (mptokensChanged != 0)
-
1303 {
-
1304 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
-
1305 "despite failure of the transaction.";
-
1306 return false;
-
1307 }
-
1308 }
-
1309
-
1310 return true;
-
1311}
-
1312
-
1313//------------------------------------------------------------------------------
+
1237//------------------------------------------------------------------------------
+
1238
+
1239void
+
1240ValidClawback::visitEntry(
+
1241 bool,
+
1242 std::shared_ptr<SLE const> const& before,
+
1243 std::shared_ptr<SLE const> const&)
+
1244{
+
1245 if (before && before->getType() == ltRIPPLE_STATE)
+
1246 trustlinesChanged++;
+
1247
+
1248 if (before && before->getType() == ltMPTOKEN)
+
1249 mptokensChanged++;
+
1250}
+
1251
+
1252bool
+
1253ValidClawback::finalize(
+
1254 STTx const& tx,
+
1255 TER const result,
+
1256 XRPAmount const,
+
1257 ReadView const& view,
+
1258 beast::Journal const& j)
+
1259{
+
1260 if (tx.getTxnType() != ttCLAWBACK)
+
1261 return true;
+
1262
+
1263 if (result == tesSUCCESS)
+
1264 {
+
1265 if (trustlinesChanged > 1)
+
1266 {
+
1267 JLOG(j.fatal())
+
1268 << "Invariant failed: more than one trustline changed.";
+
1269 return false;
+
1270 }
+
1271
+
1272 if (mptokensChanged > 1)
+
1273 {
+
1274 JLOG(j.fatal())
+
1275 << "Invariant failed: more than one mptokens changed.";
+
1276 return false;
+
1277 }
+
1278
+
1279 if (trustlinesChanged == 1)
+
1280 {
+
1281 AccountID const issuer = tx.getAccountID(sfAccount);
+
1282 STAmount const& amount = tx.getFieldAmount(sfAmount);
+
1283 AccountID const& holder = amount.getIssuer();
+
1284 STAmount const holderBalance = accountHolds(
+
1285 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
+
1286
+
1287 if (holderBalance.signum() < 0)
+
1288 {
+
1289 JLOG(j.fatal())
+
1290 << "Invariant failed: trustline balance is negative";
+
1291 return false;
+
1292 }
+
1293 }
+
1294 }
+
1295 else
+
1296 {
+
1297 if (trustlinesChanged != 0)
+
1298 {
+
1299 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
+
1300 "despite failure of the transaction.";
+
1301 return false;
+
1302 }
+
1303
+
1304 if (mptokensChanged != 0)
+
1305 {
+
1306 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
+
1307 "despite failure of the transaction.";
+
1308 return false;
+
1309 }
+
1310 }
+
1311
+
1312 return true;
+
1313}
1314
-
1315void
-
1316ValidMPTIssuance::visitEntry(
-
1317 bool isDelete,
-
1318 std::shared_ptr<SLE const> const& before,
-
1319 std::shared_ptr<SLE const> const& after)
-
1320{
-
1321 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
-
1322 {
-
1323 if (isDelete)
-
1324 mptIssuancesDeleted_++;
-
1325 else if (!before)
-
1326 mptIssuancesCreated_++;
-
1327 }
-
1328
-
1329 if (after && after->getType() == ltMPTOKEN)
-
1330 {
-
1331 if (isDelete)
-
1332 mptokensDeleted_++;
-
1333 else if (!before)
-
1334 mptokensCreated_++;
-
1335 }
-
1336}
-
1337
-
1338bool
-
1339ValidMPTIssuance::finalize(
-
1340 STTx const& tx,
-
1341 TER const result,
-
1342 XRPAmount const _fee,
-
1343 ReadView const& _view,
-
1344 beast::Journal const& j)
-
1345{
-
1346 if (result == tesSUCCESS)
-
1347 {
-
1348 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_CREATE ||
-
1349 tx.getTxnType() == ttVAULT_CREATE)
-
1350 {
-
1351 if (mptIssuancesCreated_ == 0)
-
1352 {
-
1353 JLOG(j.fatal()) << "Invariant failed: transaction "
-
1354 "succeeded without creating a MPT issuance";
-
1355 }
-
1356 else if (mptIssuancesDeleted_ != 0)
-
1357 {
-
1358 JLOG(j.fatal()) << "Invariant failed: transaction "
-
1359 "succeeded while removing MPT issuances";
-
1360 }
-
1361 else if (mptIssuancesCreated_ > 1)
-
1362 {
-
1363 JLOG(j.fatal()) << "Invariant failed: transaction "
-
1364 "succeeded but created multiple issuances";
-
1365 }
-
1366
-
1367 return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
-
1368 }
-
1369
-
1370 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_DESTROY ||
-
1371 tx.getTxnType() == ttVAULT_DELETE)
-
1372 {
-
1373 if (mptIssuancesDeleted_ == 0)
-
1374 {
-
1375 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
-
1376 "succeeded without removing a MPT issuance";
-
1377 }
-
1378 else if (mptIssuancesCreated_ > 0)
-
1379 {
-
1380 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
-
1381 "succeeded while creating MPT issuances";
-
1382 }
-
1383 else if (mptIssuancesDeleted_ > 1)
-
1384 {
-
1385 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
-
1386 "succeeded but deleted multiple issuances";
-
1387 }
-
1388
-
1389 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
-
1390 }
-
1391
-
1392 if (tx.getTxnType() == ttMPTOKEN_AUTHORIZE ||
-
1393 tx.getTxnType() == ttVAULT_DEPOSIT)
-
1394 {
-
1395 bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
-
1396
-
1397 if (mptIssuancesCreated_ > 0)
-
1398 {
-
1399 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
-
1400 "succeeded but created MPT issuances";
-
1401 return false;
-
1402 }
-
1403 else if (mptIssuancesDeleted_ > 0)
-
1404 {
-
1405 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
-
1406 "succeeded but deleted issuances";
-
1407 return false;
-
1408 }
-
1409 else if (
-
1410 submittedByIssuer &&
-
1411 (mptokensCreated_ > 0 || mptokensDeleted_ > 0))
-
1412 {
-
1413 JLOG(j.fatal())
-
1414 << "Invariant failed: MPT authorize submitted by issuer "
-
1415 "succeeded but created/deleted mptokens";
-
1416 return false;
-
1417 }
-
1418 else if (
-
1419 !submittedByIssuer && (tx.getTxnType() != ttVAULT_DEPOSIT) &&
-
1420 (mptokensCreated_ + mptokensDeleted_ != 1))
-
1421 {
-
1422 // if the holder submitted this tx, then a mptoken must be
-
1423 // either created or deleted.
-
1424 JLOG(j.fatal())
-
1425 << "Invariant failed: MPT authorize submitted by holder "
-
1426 "succeeded but created/deleted bad number of mptokens";
-
1427 return false;
-
1428 }
-
1429
-
1430 return true;
-
1431 }
-
1432
-
1433 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_SET)
-
1434 {
-
1435 if (mptIssuancesDeleted_ > 0)
-
1436 {
-
1437 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
-
1438 "succeeded while removing MPT issuances";
-
1439 }
-
1440 else if (mptIssuancesCreated_ > 0)
-
1441 {
-
1442 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
-
1443 "succeeded while creating MPT issuances";
-
1444 }
-
1445 else if (mptokensDeleted_ > 0)
-
1446 {
-
1447 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
-
1448 "succeeded while removing MPTokens";
-
1449 }
-
1450 else if (mptokensCreated_ > 0)
-
1451 {
-
1452 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
-
1453 "succeeded while creating MPTokens";
-
1454 }
-
1455
-
1456 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
-
1457 mptokensCreated_ == 0 && mptokensDeleted_ == 0;
-
1458 }
-
1459 }
-
1460
-
1461 if (mptIssuancesCreated_ != 0)
-
1462 {
-
1463 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
-
1464 }
-
1465 else if (mptIssuancesDeleted_ != 0)
-
1466 {
-
1467 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
-
1468 }
-
1469 else if (mptokensCreated_ != 0)
-
1470 {
-
1471 JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
-
1472 }
-
1473 else if (mptokensDeleted_ != 0)
-
1474 {
-
1475 JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
-
1476 }
-
1477
-
1478 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
-
1479 mptokensCreated_ == 0 && mptokensDeleted_ == 0;
-
1480}
-
1481
-
1482//------------------------------------------------------------------------------
+
1315//------------------------------------------------------------------------------
+
1316
+
1317void
+
1318ValidMPTIssuance::visitEntry(
+
1319 bool isDelete,
+
1320 std::shared_ptr<SLE const> const& before,
+
1321 std::shared_ptr<SLE const> const& after)
+
1322{
+
1323 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
+
1324 {
+
1325 if (isDelete)
+
1326 mptIssuancesDeleted_++;
+
1327 else if (!before)
+
1328 mptIssuancesCreated_++;
+
1329 }
+
1330
+
1331 if (after && after->getType() == ltMPTOKEN)
+
1332 {
+
1333 if (isDelete)
+
1334 mptokensDeleted_++;
+
1335 else if (!before)
+
1336 mptokensCreated_++;
+
1337 }
+
1338}
+
1339
+
1340bool
+
1341ValidMPTIssuance::finalize(
+
1342 STTx const& tx,
+
1343 TER const result,
+
1344 XRPAmount const _fee,
+
1345 ReadView const& _view,
+
1346 beast::Journal const& j)
+
1347{
+
1348 if (result == tesSUCCESS)
+
1349 {
+
1350 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_CREATE ||
+
1351 tx.getTxnType() == ttVAULT_CREATE)
+
1352 {
+
1353 if (mptIssuancesCreated_ == 0)
+
1354 {
+
1355 JLOG(j.fatal()) << "Invariant failed: transaction "
+
1356 "succeeded without creating a MPT issuance";
+
1357 }
+
1358 else if (mptIssuancesDeleted_ != 0)
+
1359 {
+
1360 JLOG(j.fatal()) << "Invariant failed: transaction "
+
1361 "succeeded while removing MPT issuances";
+
1362 }
+
1363 else if (mptIssuancesCreated_ > 1)
+
1364 {
+
1365 JLOG(j.fatal()) << "Invariant failed: transaction "
+
1366 "succeeded but created multiple issuances";
+
1367 }
+
1368
+
1369 return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
+
1370 }
+
1371
+
1372 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_DESTROY ||
+
1373 tx.getTxnType() == ttVAULT_DELETE)
+
1374 {
+
1375 if (mptIssuancesDeleted_ == 0)
+
1376 {
+
1377 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
+
1378 "succeeded without removing a MPT issuance";
+
1379 }
+
1380 else if (mptIssuancesCreated_ > 0)
+
1381 {
+
1382 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
+
1383 "succeeded while creating MPT issuances";
+
1384 }
+
1385 else if (mptIssuancesDeleted_ > 1)
+
1386 {
+
1387 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
+
1388 "succeeded but deleted multiple issuances";
+
1389 }
+
1390
+
1391 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
+
1392 }
+
1393
+
1394 if (tx.getTxnType() == ttMPTOKEN_AUTHORIZE ||
+
1395 tx.getTxnType() == ttVAULT_DEPOSIT)
+
1396 {
+
1397 bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
+
1398
+
1399 if (mptIssuancesCreated_ > 0)
+
1400 {
+
1401 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
+
1402 "succeeded but created MPT issuances";
+
1403 return false;
+
1404 }
+
1405 else if (mptIssuancesDeleted_ > 0)
+
1406 {
+
1407 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
+
1408 "succeeded but deleted issuances";
+
1409 return false;
+
1410 }
+
1411 else if (
+
1412 submittedByIssuer &&
+
1413 (mptokensCreated_ > 0 || mptokensDeleted_ > 0))
+
1414 {
+
1415 JLOG(j.fatal())
+
1416 << "Invariant failed: MPT authorize submitted by issuer "
+
1417 "succeeded but created/deleted mptokens";
+
1418 return false;
+
1419 }
+
1420 else if (
+
1421 !submittedByIssuer && (tx.getTxnType() != ttVAULT_DEPOSIT) &&
+
1422 (mptokensCreated_ + mptokensDeleted_ != 1))
+
1423 {
+
1424 // if the holder submitted this tx, then a mptoken must be
+
1425 // either created or deleted.
+
1426 JLOG(j.fatal())
+
1427 << "Invariant failed: MPT authorize submitted by holder "
+
1428 "succeeded but created/deleted bad number of mptokens";
+
1429 return false;
+
1430 }
+
1431
+
1432 return true;
+
1433 }
+
1434
+
1435 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_SET)
+
1436 {
+
1437 if (mptIssuancesDeleted_ > 0)
+
1438 {
+
1439 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
+
1440 "succeeded while removing MPT issuances";
+
1441 }
+
1442 else if (mptIssuancesCreated_ > 0)
+
1443 {
+
1444 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
+
1445 "succeeded while creating MPT issuances";
+
1446 }
+
1447 else if (mptokensDeleted_ > 0)
+
1448 {
+
1449 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
+
1450 "succeeded while removing MPTokens";
+
1451 }
+
1452 else if (mptokensCreated_ > 0)
+
1453 {
+
1454 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
+
1455 "succeeded while creating MPTokens";
+
1456 }
+
1457
+
1458 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
+
1459 mptokensCreated_ == 0 && mptokensDeleted_ == 0;
+
1460 }
+
1461 }
+
1462
+
1463 if (mptIssuancesCreated_ != 0)
+
1464 {
+
1465 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
+
1466 }
+
1467 else if (mptIssuancesDeleted_ != 0)
+
1468 {
+
1469 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
+
1470 }
+
1471 else if (mptokensCreated_ != 0)
+
1472 {
+
1473 JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
+
1474 }
+
1475 else if (mptokensDeleted_ != 0)
+
1476 {
+
1477 JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
+
1478 }
+
1479
+
1480 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
+
1481 mptokensCreated_ == 0 && mptokensDeleted_ == 0;
+
1482}
1483
-
1484void
-
1485ValidPermissionedDomain::visitEntry(
-
1486 bool,
-
1487 std::shared_ptr<SLE const> const& before,
-
1488 std::shared_ptr<SLE const> const& after)
-
1489{
-
1490 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
-
1491 return;
-
1492 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
+
1484//------------------------------------------------------------------------------
+
1485
+
1486void
+
1487ValidPermissionedDomain::visitEntry(
+
1488 bool,
+
1489 std::shared_ptr<SLE const> const& before,
+
1490 std::shared_ptr<SLE const> const& after)
+
1491{
+
1492 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
1493 return;
-
1494
-
1495 auto check = [](SleStatus& sleStatus,
-
1496 std::shared_ptr<SLE const> const& sle) {
-
1497 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
-
1498 sleStatus.credentialsSize_ = credentials.size();
-
1499 auto const sorted = credentials::makeSorted(credentials);
-
1500 sleStatus.isUnique_ = !sorted.empty();
-
1501
-
1502 // If array have duplicates then all the other checks are invalid
-
1503 sleStatus.isSorted_ = false;
-
1504
-
1505 if (sleStatus.isUnique_)
-
1506 {
-
1507 unsigned i = 0;
-
1508 for (auto const& cred : sorted)
-
1509 {
-
1510 auto const& credTx = credentials[i++];
-
1511 sleStatus.isSorted_ = (cred.first == credTx[sfIssuer]) &&
-
1512 (cred.second == credTx[sfCredentialType]);
-
1513 if (!sleStatus.isSorted_)
-
1514 break;
-
1515 }
-
1516 }
-
1517 };
-
1518
-
1519 if (before)
-
1520 {
-
1521 sleStatus_[0] = SleStatus();
-
1522 check(*sleStatus_[0], after);
-
1523 }
-
1524
-
1525 if (after)
-
1526 {
-
1527 sleStatus_[1] = SleStatus();
-
1528 check(*sleStatus_[1], after);
-
1529 }
-
1530}
-
1531
-
1532bool
-
1533ValidPermissionedDomain::finalize(
-
1534 STTx const& tx,
-
1535 TER const result,
-
1536 XRPAmount const,
-
1537 ReadView const& view,
-
1538 beast::Journal const& j)
-
1539{
-
1540 if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS)
-
1541 return true;
-
1542
-
1543 auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
-
1544 if (!sleStatus.credentialsSize_)
-
1545 {
-
1546 JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
-
1547 "no rules.";
-
1548 return false;
-
1549 }
-
1550
-
1551 if (sleStatus.credentialsSize_ >
-
1552 maxPermissionedDomainCredentialsArraySize)
-
1553 {
-
1554 JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
-
1555 "credentials size "
-
1556 << sleStatus.credentialsSize_;
-
1557 return false;
-
1558 }
-
1559
-
1560 if (!sleStatus.isUnique_)
-
1561 {
-
1562 JLOG(j.fatal())
-
1563 << "Invariant failed: permissioned domain credentials "
-
1564 "aren't unique";
-
1565 return false;
-
1566 }
-
1567
-
1568 if (!sleStatus.isSorted_)
-
1569 {
-
1570 JLOG(j.fatal())
-
1571 << "Invariant failed: permissioned domain credentials "
-
1572 "aren't sorted";
-
1573 return false;
-
1574 }
-
1575
-
1576 return true;
-
1577 };
-
1578
-
1579 return (sleStatus_[0] ? check(*sleStatus_[0], j) : true) &&
-
1580 (sleStatus_[1] ? check(*sleStatus_[1], j) : true);
-
1581}
-
1582
-
1583void
-
1584ValidPermissionedDEX::visitEntry(
-
1585 bool,
-
1586 std::shared_ptr<SLE const> const& before,
-
1587 std::shared_ptr<SLE const> const& after)
-
1588{
-
1589 if (after && after->getType() == ltDIR_NODE)
-
1590 {
-
1591 if (after->isFieldPresent(sfDomainID))
-
1592 domains_.insert(after->getFieldH256(sfDomainID));
-
1593 }
-
1594
-
1595 if (after && after->getType() == ltOFFER)
-
1596 {
-
1597 if (after->isFieldPresent(sfDomainID))
-
1598 domains_.insert(after->getFieldH256(sfDomainID));
-
1599 else
-
1600 regularOffers_ = true;
-
1601
-
1602 // if a hybrid offer is missing domain or additional book, there's
-
1603 // something wrong
-
1604 if (after->isFlag(lsfHybrid) &&
-
1605 (!after->isFieldPresent(sfDomainID) ||
-
1606 !after->isFieldPresent(sfAdditionalBooks) ||
-
1607 after->getFieldArray(sfAdditionalBooks).size() > 1))
-
1608 badHybrids_ = true;
-
1609 }
-
1610}
-
1611
-
1612bool
-
1613ValidPermissionedDEX::finalize(
-
1614 STTx const& tx,
-
1615 TER const result,
-
1616 XRPAmount const,
-
1617 ReadView const& view,
-
1618 beast::Journal const& j)
-
1619{
-
1620 auto const txType = tx.getTxnType();
-
1621 if ((txType != ttPAYMENT && txType != ttOFFER_CREATE) ||
-
1622 result != tesSUCCESS)
-
1623 return true;
-
1624
-
1625 // For each offercreate transaction, check if
-
1626 // permissioned offers are valid
-
1627 if (txType == ttOFFER_CREATE && badHybrids_)
-
1628 {
-
1629 JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed";
-
1630 return false;
-
1631 }
-
1632
-
1633 if (!tx.isFieldPresent(sfDomainID))
-
1634 return true;
-
1635
-
1636 auto const domain = tx.getFieldH256(sfDomainID);
+
1494 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
+
1495 return;
+
1496
+
1497 auto check = [](SleStatus& sleStatus,
+
1498 std::shared_ptr<SLE const> const& sle) {
+
1499 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
+
1500 sleStatus.credentialsSize_ = credentials.size();
+
1501 auto const sorted = credentials::makeSorted(credentials);
+
1502 sleStatus.isUnique_ = !sorted.empty();
+
1503
+
1504 // If array have duplicates then all the other checks are invalid
+
1505 sleStatus.isSorted_ = false;
+
1506
+
1507 if (sleStatus.isUnique_)
+
1508 {
+
1509 unsigned i = 0;
+
1510 for (auto const& cred : sorted)
+
1511 {
+
1512 auto const& credTx = credentials[i++];
+
1513 sleStatus.isSorted_ = (cred.first == credTx[sfIssuer]) &&
+
1514 (cred.second == credTx[sfCredentialType]);
+
1515 if (!sleStatus.isSorted_)
+
1516 break;
+
1517 }
+
1518 }
+
1519 };
+
1520
+
1521 if (before)
+
1522 {
+
1523 sleStatus_[0] = SleStatus();
+
1524 check(*sleStatus_[0], after);
+
1525 }
+
1526
+
1527 if (after)
+
1528 {
+
1529 sleStatus_[1] = SleStatus();
+
1530 check(*sleStatus_[1], after);
+
1531 }
+
1532}
+
1533
+
1534bool
+
1535ValidPermissionedDomain::finalize(
+
1536 STTx const& tx,
+
1537 TER const result,
+
1538 XRPAmount const,
+
1539 ReadView const& view,
+
1540 beast::Journal const& j)
+
1541{
+
1542 if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS)
+
1543 return true;
+
1544
+
1545 auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
+
1546 if (!sleStatus.credentialsSize_)
+
1547 {
+
1548 JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
+
1549 "no rules.";
+
1550 return false;
+
1551 }
+
1552
+
1553 if (sleStatus.credentialsSize_ >
+
1554 maxPermissionedDomainCredentialsArraySize)
+
1555 {
+
1556 JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
+
1557 "credentials size "
+
1558 << sleStatus.credentialsSize_;
+
1559 return false;
+
1560 }
+
1561
+
1562 if (!sleStatus.isUnique_)
+
1563 {
+
1564 JLOG(j.fatal())
+
1565 << "Invariant failed: permissioned domain credentials "
+
1566 "aren't unique";
+
1567 return false;
+
1568 }
+
1569
+
1570 if (!sleStatus.isSorted_)
+
1571 {
+
1572 JLOG(j.fatal())
+
1573 << "Invariant failed: permissioned domain credentials "
+
1574 "aren't sorted";
+
1575 return false;
+
1576 }
+
1577
+
1578 return true;
+
1579 };
+
1580
+
1581 return (sleStatus_[0] ? check(*sleStatus_[0], j) : true) &&
+
1582 (sleStatus_[1] ? check(*sleStatus_[1], j) : true);
+
1583}
+
1584
+
1585void
+
1586ValidPermissionedDEX::visitEntry(
+
1587 bool,
+
1588 std::shared_ptr<SLE const> const& before,
+
1589 std::shared_ptr<SLE const> const& after)
+
1590{
+
1591 if (after && after->getType() == ltDIR_NODE)
+
1592 {
+
1593 if (after->isFieldPresent(sfDomainID))
+
1594 domains_.insert(after->getFieldH256(sfDomainID));
+
1595 }
+
1596
+
1597 if (after && after->getType() == ltOFFER)
+
1598 {
+
1599 if (after->isFieldPresent(sfDomainID))
+
1600 domains_.insert(after->getFieldH256(sfDomainID));
+
1601 else
+
1602 regularOffers_ = true;
+
1603
+
1604 // if a hybrid offer is missing domain or additional book, there's
+
1605 // something wrong
+
1606 if (after->isFlag(lsfHybrid) &&
+
1607 (!after->isFieldPresent(sfDomainID) ||
+
1608 !after->isFieldPresent(sfAdditionalBooks) ||
+
1609 after->getFieldArray(sfAdditionalBooks).size() > 1))
+
1610 badHybrids_ = true;
+
1611 }
+
1612}
+
1613
+
1614bool
+
1615ValidPermissionedDEX::finalize(
+
1616 STTx const& tx,
+
1617 TER const result,
+
1618 XRPAmount const,
+
1619 ReadView const& view,
+
1620 beast::Journal const& j)
+
1621{
+
1622 auto const txType = tx.getTxnType();
+
1623 if ((txType != ttPAYMENT && txType != ttOFFER_CREATE) ||
+
1624 result != tesSUCCESS)
+
1625 return true;
+
1626
+
1627 // For each offercreate transaction, check if
+
1628 // permissioned offers are valid
+
1629 if (txType == ttOFFER_CREATE && badHybrids_)
+
1630 {
+
1631 JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed";
+
1632 return false;
+
1633 }
+
1634
+
1635 if (!tx.isFieldPresent(sfDomainID))
+
1636 return true;
1637
-
1638 if (!view.exists(keylet::permissionedDomain(domain)))
-
1639 {
-
1640 JLOG(j.fatal()) << "Invariant failed: domain doesn't exist";
-
1641 return false;
-
1642 }
-
1643
-
1644 // for both payment and offercreate, there shouldn't be another domain
-
1645 // that's different from the domain specified
-
1646 for (auto const& d : domains_)
-
1647 {
-
1648 if (d != domain)
-
1649 {
-
1650 JLOG(j.fatal()) << "Invariant failed: transaction"
-
1651 " consumed wrong domains";
-
1652 return false;
-
1653 }
-
1654 }
-
1655
-
1656 if (regularOffers_)
-
1657 {
-
1658 JLOG(j.fatal()) << "Invariant failed: domain transaction"
-
1659 " affected regular offers";
-
1660 return false;
-
1661 }
-
1662
-
1663 return true;
-
1664}
-
1665
-
1666} // namespace ripple
+
1638 auto const domain = tx.getFieldH256(sfDomainID);
+
1639
+
1640 if (!view.exists(keylet::permissionedDomain(domain)))
+
1641 {
+
1642 JLOG(j.fatal()) << "Invariant failed: domain doesn't exist";
+
1643 return false;
+
1644 }
+
1645
+
1646 // for both payment and offercreate, there shouldn't be another domain
+
1647 // that's different from the domain specified
+
1648 for (auto const& d : domains_)
+
1649 {
+
1650 if (d != domain)
+
1651 {
+
1652 JLOG(j.fatal()) << "Invariant failed: transaction"
+
1653 " consumed wrong domains";
+
1654 return false;
+
1655 }
+
1656 }
+
1657
+
1658 if (regularOffers_)
+
1659 {
+
1660 JLOG(j.fatal()) << "Invariant failed: domain transaction"
+
1661 " affected regular offers";
+
1662 return false;
+
1663 }
+
1664
+
1665 return true;
+
1666}
+
1667
+
1668void
+
1669ValidAMM::visitEntry(
+
1670 bool isDelete,
+
1671 std::shared_ptr<SLE const> const& before,
+
1672 std::shared_ptr<SLE const> const& after)
+
1673{
+
1674 if (isDelete)
+
1675 return;
+
1676
+
1677 if (after)
+
1678 {
+
1679 auto const type = after->getType();
+
1680 // AMM object changed
+
1681 if (type == ltAMM)
+
1682 {
+
1683 ammAccount_ = after->getAccountID(sfAccount);
+
1684 lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
+
1685 }
+
1686 // AMM pool changed
+
1687 else if (
+
1688 (type == ltRIPPLE_STATE && after->getFlags() & lsfAMMNode) ||
+
1689 (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)))
+
1690 {
+
1691 ammPoolChanged_ = true;
+
1692 }
+
1693 }
+
1694
+
1695 if (before)
+
1696 {
+
1697 // AMM object changed
+
1698 if (before->getType() == ltAMM)
+
1699 {
+
1700 lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance);
+
1701 }
+
1702 }
+
1703}
+
1704
+
1705static bool
+
1706validBalances(
+
1707 STAmount const& amount,
+
1708 STAmount const& amount2,
+
1709 STAmount const& lptAMMBalance,
+
1710 ValidAMM::ZeroAllowed zeroAllowed)
+
1711{
+
1712 bool const positive = amount > beast::zero && amount2 > beast::zero &&
+
1713 lptAMMBalance > beast::zero;
+
1714 if (zeroAllowed == ValidAMM::ZeroAllowed::Yes)
+
1715 return positive ||
+
1716 (amount == beast::zero && amount2 == beast::zero &&
+
1717 lptAMMBalance == beast::zero);
+
1718 return positive;
+
1719}
+
1720
+
1721bool
+
1722ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const
+
1723{
+
1724 if (lptAMMBalanceAfter_ != lptAMMBalanceBefore_ || ammPoolChanged_)
+
1725 {
+
1726 // LPTokens and the pool can not change on vote
+
1727 // LCOV_EXCL_START
+
1728 JLOG(j.error()) << "AMMVote invariant failed: "
+
1729 << lptAMMBalanceBefore_.value_or(STAmount{}) << " "
+
1730 << lptAMMBalanceAfter_.value_or(STAmount{}) << " "
+
1731 << ammPoolChanged_;
+
1732 if (enforce)
+
1733 return false;
+
1734 // LCOV_EXCL_STOP
+
1735 }
+
1736
+
1737 return true;
+
1738}
+
1739
+
1740bool
+
1741ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
+
1742{
+
1743 if (ammPoolChanged_)
+
1744 {
+
1745 // The pool can not change on bid
+
1746 // LCOV_EXCL_START
+
1747 JLOG(j.error()) << "AMMBid invariant failed: pool changed";
+
1748 if (enforce)
+
1749 return false;
+
1750 // LCOV_EXCL_STOP
+
1751 }
+
1752 // LPTokens are burnt, therefore there should be fewer LPTokens
+
1753 else if (
+
1754 lptAMMBalanceBefore_ && lptAMMBalanceAfter_ &&
+
1755 (*lptAMMBalanceAfter_ > *lptAMMBalanceBefore_ ||
+
1756 *lptAMMBalanceAfter_ <= beast::zero))
+
1757 {
+
1758 // LCOV_EXCL_START
+
1759 JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_
+
1760 << " " << *lptAMMBalanceAfter_;
+
1761 if (enforce)
+
1762 return false;
+
1763 // LCOV_EXCL_STOP
+
1764 }
+
1765
+
1766 return true;
+
1767}
+
1768
+
1769bool
+
1770ValidAMM::finalizeCreate(
+
1771 STTx const& tx,
+
1772 ReadView const& view,
+
1773 bool enforce,
+
1774 beast::Journal const& j) const
+
1775{
+
1776 if (!ammAccount_)
+
1777 {
+
1778 // LCOV_EXCL_START
+
1779 JLOG(j.error())
+
1780 << "AMMCreate invariant failed: AMM object is not created";
+
1781 if (enforce)
+
1782 return false;
+
1783 // LCOV_EXCL_STOP
+
1784 }
+
1785 else
+
1786 {
+
1787 auto const [amount, amount2] = ammPoolHolds(
+
1788 view,
+
1789 *ammAccount_,
+
1790 tx[sfAmount].get<Issue>(),
+
1791 tx[sfAmount2].get<Issue>(),
+
1792 fhIGNORE_FREEZE,
+
1793 j);
+
1794 // Create invariant:
+
1795 // sqrt(amount * amount2) == LPTokens
+
1796 // all balances are greater than zero
+
1797 if (!validBalances(
+
1798 amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
+
1799 ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) !=
+
1800 *lptAMMBalanceAfter_)
+
1801 {
+
1802 JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " "
+
1803 << amount2 << " " << *lptAMMBalanceAfter_;
+
1804 if (enforce)
+
1805 return false;
+
1806 }
+
1807 }
+
1808
+
1809 return true;
+
1810}
+
1811
+
1812bool
+
1813ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const
+
1814{
+
1815 if (ammAccount_)
+
1816 {
+
1817 // LCOV_EXCL_START
+
1818 std::string const msg = (res == tesSUCCESS)
+
1819 ? "AMM object is not deleted on tesSUCCESS"
+
1820 : "AMM object is changed on tecINCOMPLETE";
+
1821 JLOG(j.error()) << "AMMDelete invariant failed: " << msg;
+
1822 if (enforce)
+
1823 return false;
+
1824 // LCOV_EXCL_STOP
+
1825 }
+
1826
+
1827 return true;
+
1828}
+
1829
+
1830bool
+
1831ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const
+
1832{
+
1833 if (ammAccount_)
+
1834 {
+
1835 // LCOV_EXCL_START
+
1836 JLOG(j.error()) << "AMM swap invariant failed: AMM object changed";
+
1837 if (enforce)
+
1838 return false;
+
1839 // LCOV_EXCL_STOP
+
1840 }
+
1841
+
1842 return true;
+
1843}
+
1844
+
1845bool
+
1846ValidAMM::generalInvariant(
+
1847 ripple::STTx const& tx,
+
1848 ripple::ReadView const& view,
+
1849 ZeroAllowed zeroAllowed,
+
1850 beast::Journal const& j) const
+
1851{
+
1852 auto const [amount, amount2] = ammPoolHolds(
+
1853 view,
+
1854 *ammAccount_,
+
1855 tx[sfAsset].get<Issue>(),
+
1856 tx[sfAsset2].get<Issue>(),
+
1857 fhIGNORE_FREEZE,
+
1858 j);
+
1859 // Deposit and Withdrawal invariant:
+
1860 // sqrt(amount * amount2) >= LPTokens
+
1861 // all balances are greater than zero
+
1862 // unless on last withdrawal
+
1863 auto const poolProductMean = root2(amount * amount2);
+
1864 bool const nonNegativeBalances =
+
1865 validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed);
+
1866 bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_;
+
1867 // Allow for a small relative error if strongInvariantCheck fails
+
1868 auto weakInvariantCheck = [&]() {
+
1869 return *lptAMMBalanceAfter_ != beast::zero &&
+
1870 withinRelativeDistance(
+
1871 poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11});
+
1872 };
+
1873 if (!nonNegativeBalances ||
+
1874 (!strongInvariantCheck && !weakInvariantCheck()))
+
1875 {
+
1876 JLOG(j.error()) << "AMM " << tx.getTxnType() << " invariant failed: "
+
1877 << tx.getHash(HashPrefix::transactionID) << " "
+
1878 << ammPoolChanged_ << " " << amount << " " << amount2
+
1879 << " " << poolProductMean << " "
+
1880 << lptAMMBalanceAfter_->getText() << " "
+
1881 << ((*lptAMMBalanceAfter_ == beast::zero)
+
1882 ? Number{1}
+
1883 : ((*lptAMMBalanceAfter_ - poolProductMean) /
+
1884 poolProductMean));
+
1885 return false;
+
1886 }
+
1887
+
1888 return true;
+
1889}
+
1890
+
1891bool
+
1892ValidAMM::finalizeDeposit(
+
1893 ripple::STTx const& tx,
+
1894 ripple::ReadView const& view,
+
1895 bool enforce,
+
1896 beast::Journal const& j) const
+
1897{
+
1898 if (!ammAccount_)
+
1899 {
+
1900 // LCOV_EXCL_START
+
1901 JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted";
+
1902 if (enforce)
+
1903 return false;
+
1904 // LCOV_EXCL_STOP
+
1905 }
+
1906 else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce)
+
1907 return false;
+
1908
+
1909 return true;
+
1910}
+
1911
+
1912bool
+
1913ValidAMM::finalizeWithdraw(
+
1914 ripple::STTx const& tx,
+
1915 ripple::ReadView const& view,
+
1916 bool enforce,
+
1917 beast::Journal const& j) const
+
1918{
+
1919 if (!ammAccount_)
+
1920 {
+
1921 // Last Withdraw or Clawback deleted AMM
+
1922 }
+
1923 else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j))
+
1924 {
+
1925 if (enforce)
+
1926 return false;
+
1927 }
+
1928
+
1929 return true;
+
1930}
+
1931
+
1932bool
+
1933ValidAMM::finalize(
+
1934 STTx const& tx,
+
1935 TER const result,
+
1936 XRPAmount const,
+
1937 ReadView const& view,
+
1938 beast::Journal const& j)
+
1939{
+
1940 // Delete may return tecINCOMPLETE if there are too many
+
1941 // trustlines to delete.
+
1942 if (result != tesSUCCESS && result != tecINCOMPLETE)
+
1943 return true;
+
1944
+
1945 bool const enforce = view.rules().enabled(fixAMMv1_3);
+
1946
+
1947 switch (tx.getTxnType())
+
1948 {
+
1949 case ttAMM_CREATE:
+
1950 return finalizeCreate(tx, view, enforce, j);
+
1951 case ttAMM_DEPOSIT:
+
1952 return finalizeDeposit(tx, view, enforce, j);
+
1953 case ttAMM_CLAWBACK:
+
1954 case ttAMM_WITHDRAW:
+
1955 return finalizeWithdraw(tx, view, enforce, j);
+
1956 case ttAMM_BID:
+
1957 return finalizeBid(enforce, j);
+
1958 case ttAMM_VOTE:
+
1959 return finalizeVote(enforce, j);
+
1960 case ttAMM_DELETE:
+
1961 return finalizeDelete(enforce, result, j);
+
1962 case ttCHECK_CASH:
+
1963 case ttOFFER_CREATE:
+
1964 case ttPAYMENT:
+
1965 return finalizeDEX(enforce, j);
+
1966 default:
+
1967 break;
+
1968 }
+
1969
+
1970 return true;
+
1971}
+
1972
+
1973} // namespace ripple
+
A generic endpoint for log messages.
Definition: Journal.h:60
Stream fatal() const
Definition: Journal.h:352
+
Stream error() const
Definition: Journal.h:346
Stream debug() const
Definition: Journal.h:328
-
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 &)
+
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 &)
std::vector< std::shared_ptr< SLE const > > accountsDeleted_
std::uint32_t accountsDeleted_
-
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 &)
+
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 &)
A currency issued by an account.
Definition: Issue.h:36
Item const * findByType(KeyType type) const
Retrieve a format based on its type.
Definition: KnownFormats.h:129
bool invalidTypeAdded_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool typeMismatch_
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
static LedgerFormats const & getInstance()
std::uint32_t afterMintedTotal
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t afterBurnedTotal
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t beforeBurnedTotal
std::uint32_t beforeMintedTotal
-
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 &)
+
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 &)
bool bad_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool deepFreezeWithoutFreeze_
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool xrpTrustLine_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
-
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 &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
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 &)
bool bad_
+
Definition: Number.h:36
A view into a ledger.
Definition: ReadView.h:52
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual std::optional< key_type > succ(key_type const &key, std::optional< key_type > const &last=std::nullopt) const =0
Return the key of the next state item.
@@ -1792,34 +2102,52 @@ $(function() {
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
Definition: STAmount.h:520
AccountID getAccountID(SField const &field) const
Definition: STObject.cpp:651
STAmount const & getFieldAmount(SField const &field) const
Definition: STObject.cpp:665
+
uint256 getHash(HashPrefix prefix) const
Definition: STObject.cpp:395
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
uint256 getFieldH256(SField const &field) const
Definition: STObject.cpp:645
Definition: STTx.h:48
TxType getTxnType() const
Definition: STTx.h:207
uint256 getTransactionID() const
Definition: STTx.h:219
-
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 &)
+
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 &)
std::map< AccountID, std::shared_ptr< SLE const > const > possibleIssuers_
-
bool isValidEntry(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after)
-
void recordBalance(Issue const &issue, BalanceChange change)
-
std::shared_ptr< SLE const > findIssuer(AccountID const &issuerID, ReadView const &view)
-
bool validateIssuerChanges(std::shared_ptr< SLE const > const &issuer, IssuerChanges const &changes, STTx const &tx, beast::Journal const &j, bool enforce)
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
-
STAmount calculateBalanceChange(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after, bool isDelete)
+
bool isValidEntry(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after)
+
void recordBalance(Issue const &issue, BalanceChange change)
+
std::shared_ptr< SLE const > findIssuer(AccountID const &issuerID, ReadView const &view)
+
bool validateIssuerChanges(std::shared_ptr< SLE const > const &issuer, IssuerChanges const &changes, STTx const &tx, beast::Journal const &j, bool enforce)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
STAmount calculateBalanceChange(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after, bool isDelete)
ByIssuer balanceChanges_
-
void recordBalanceChanges(std::shared_ptr< SLE const > const &after, STAmount const &balanceChange)
-
bool validateFrozenState(BalanceChange const &change, bool high, STTx const &tx, beast::Journal const &j, bool enforce, bool globalFreeze)
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void recordBalanceChanges(std::shared_ptr< SLE const > const &after, STAmount const &balanceChange)
+
bool validateFrozenState(BalanceChange const &change, bool high, STTx const &tx, beast::Journal const &j, bool enforce, bool globalFreeze)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
ZeroAllowed
+
@ Yes
+
@ No
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalizeWithdraw(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
+
bool finalizeDEX(bool enforce, beast::Journal const &) const
+
bool ammPoolChanged_
+
std::optional< STAmount > lptAMMBalanceAfter_
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
bool finalizeBid(bool enforce, beast::Journal const &) const
+
std::optional< AccountID > ammAccount_
+
bool finalizeDelete(bool enforce, TER res, beast::Journal const &) const
+
bool finalizeCreate(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
+
bool finalizeVote(bool enforce, beast::Journal const &) const
+
bool finalizeDeposit(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
+
bool generalInvariant(STTx const &, ReadView const &, ZeroAllowed zeroAllowed, beast::Journal const &) const
+
std::optional< STAmount > lptAMMBalanceBefore_
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t trustlinesChanged
std::uint32_t mptokensChanged
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t mptIssuancesCreated_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t mptokensCreated_
std::uint32_t mptIssuancesDeleted_
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t mptokensDeleted_
bool deletedFinalPage_
bool deletedLink_
@@ -1827,31 +2155,31 @@ $(function() {
bool invalidSize_
bool badURI_
bool badSort_
-
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 &)
+
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 &)
bool badLink_
bool pseudoAccount_
std::uint32_t flags_
std::uint32_t accountsCreated_
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t accountSeq_
-
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 &)
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
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 &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool regularOffers_
hash_set< uint256 > domains_
bool badHybrids_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::optional< SleStatus > sleStatus_[2]
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
Definition: XRPAmount.h:43
constexpr value_type drops() const
Returns the number of drops.
Definition: XRPAmount.h:177
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool bad_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::int64_t drops_
-
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 &)
+
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 &)
base_uint next() const
Definition: base_uint.h:455
@@ -1868,6 +2196,7 @@ $(function() {
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:118
@ fhIGNORE_FREEZE
Definition: View.h:78
TxType
Transaction type identifiers.
Definition: TxFormats.h:57
+
static bool validBalances(STAmount const &amount, STAmount const &amount2, STAmount const &lptAMMBalance, ValidAMM::ZeroAllowed zeroAllowed)
base_uint< 256 > uint256
Definition: base_uint.h:558
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition: Protocol.h:111
@ lsfHighDeepFreeze
@@ -1883,9 +2212,15 @@ $(function() {
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
std::size_t constexpr dirMaxTokensPerPage
The maximum number of items in an NFT page.
Definition: Protocol.h:63
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition: Indexes.h:381
+
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 balances.
Definition: AMMUtils.cpp:30
+
@ tecINCOMPLETE
Definition: TER.h:335
@ tesSUCCESS
Definition: TER.h:244
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:386
+
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition: AMMHelpers.cpp:25
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: View.cpp:2727
+
@ transactionID
transaction plus signature to give transaction ID
+
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition: AMMHelpers.h:129
+
Number root2(Number f)
Definition: Number.cpp:701
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct)
Definition: View.cpp:1127
@@ -1903,6 +2238,7 @@ $(function() {
std::size_t credentialsSize_
bool isUnique_
T to_string(T... args)
+
T value_or(T... args)