From d4f8ab912e69c3de2afb8da41698452e1b0bddc5 Mon Sep 17 00:00:00 2001 From: ximinez Date: Fri, 11 Apr 2025 22:25:01 +0000 Subject: [PATCH] deploy: 405f4613d8d3af5e4443aaa8f952c557a7c7a54b --- AMMCalc__test_8cpp_source.html | 2 +- AMMClawback__test_8cpp_source.html | 2 +- AMMExtended__test_8cpp_source.html | 8 +- AMMTest_8cpp_source.html | 4 +- AMM_8cpp_source.html | 8 +- AMM__test_8cpp_source.html | 13947 ++++++++-------- AccountDelete__test_8cpp_source.html | 12 +- AccountInfo__test_8cpp_source.html | 2 +- AccountLines__test_8cpp_source.html | 2 +- AccountObjects__test_8cpp_source.html | 4 +- AccountOffers__test_8cpp_source.html | 2 +- AccountTxPaging_8cpp_source.html | 43 +- AccountTxPaging_8h_source.html | 2 +- AccountTxPaging__test_8cpp_source.html | 2 +- AccountTx__test_8cpp_source.html | 8 +- AmendmentTable__test_8cpp_source.html | 2 +- Application_8cpp_source.html | 6 +- ApplyContext_8cpp_source.html | 6 +- ApplyContext_8h_source.html | 4 +- ApplyStateTable_8cpp_source.html | 4 +- ApplyStateTable_8h_source.html | 4 +- ApplyViewImpl_8cpp_source.html | 4 +- ApplyViewImpl_8h_source.html | 4 +- BatchWriter_8cpp_source.html | 10 +- BookChanges__test_8cpp_source.html | 2 +- BookDirs__test_8cpp_source.html | 2 +- BookOffers_8cpp_source.html | 2 +- Book__test_8cpp_source.html | 6 +- CTID_8h_source.html | 114 +- CanDelete_8cpp_source.html | 2 +- CanonicalTXSet_8cpp_source.html | 6 +- Check__test_8cpp_source.html | 4 +- ClosureCounter__test_8cpp_source.html | 2 +- Connect_8cpp_source.html | 2 +- ConsensusTransSetSF_8cpp_source.html | 2 +- Coroutine__test_8cpp_source.html | 2 +- Counts_8h_source.html | 6 +- Credentials__test_8cpp_source.html | 8 +- CrossingLimits__test_8cpp_source.html | 2 +- DID__test_8cpp_source.html | 6 +- DNS__test_8cpp_source.html | 2 +- Database_8cpp_source.html | 6 +- Database__test_8cpp_source.html | 2 +- DeliverMin__test_8cpp_source.html | 4 +- DeliveredAmount__test_8cpp_source.html | 6 +- DepositAuth__test_8cpp_source.html | 22 +- DepositAuthorized_8cpp_source.html | 2 +- DepositAuthorized__test_8cpp_source.html | 1132 +- Directory__test_8cpp_source.html | 2 +- ...source.html => DoManifest_8cpp_source.html | 18 +- Env_8cpp_source.html | 28 +- Env_8h_source.html | 864 +- Env__ss_8h_source.html | 2 +- Env__test_8cpp_source.html | 12 +- ErrorCodes_8cpp_source.html | 272 +- ErrorCodes_8h_source.html | 10 +- Escrow_8cpp_source.html | 1008 +- Escrow_8h_source.html | 20 +- Escrow__test_8cpp_source.html | 16 +- FixNFTokenPageLinks__test_8cpp_source.html | 4 +- Flow__test_8cpp_source.html | 6 +- FullBelowCache_8h_source.html | 6 +- GRPCServer_8cpp_source.html | 2 +- GatewayBalances__test_8cpp_source.html | 2 +- GetAggregatePrice__test_8cpp_source.html | 2 +- Handler_8cpp_source.html | 6 +- Handlers_8h_source.html | 6 +- InnerObjectFormats__test_8cpp_source.html | 2 +- InvariantCheck_8cpp_source.html | 122 +- InvariantCheck_8h_source.html | 10 +- JSONRPC__test_8cpp_source.html | 8 +- KeyGeneration__test_8cpp_source.html | 2 +- LedgerEntry_8cpp_source.html | 1604 +- LedgerEntry__test_8cpp_source.html | 2828 ++++ LedgerHistory__test_8cpp_source.html | 4 +- LedgerRPC__test_8cpp_source.html | 4045 +---- LedgerReplay__test_8cpp_source.html | 4 +- LedgerRequestRPC__test_8cpp_source.html | 2 +- Livecache__test_8cpp_source.html | 10 +- MPToken__test_8cpp_source.html | 10 +- ManifestRPC__test_8cpp_source.html | 2 +- ...p_source.html => Manifest_8cpp_source.html | 38 +- Manifest_8h_source.html | 30 +- Manifest__test_8cpp_source.html | 20 +- Message_8cpp_source.html | 8 +- Message_8h_source.html | 2 +- MultiSign__test_8cpp_source.html | 4 +- NFTokenBurn__test_8cpp_source.html | 2 +- NFTokenDir__test_8cpp_source.html | 2 +- NFToken__test_8cpp_source.html | 4 +- NegativeUNL__test_8cpp_source.html | 4 +- NetworkID__test_8cpp_source.html | 8 +- NetworkOPs_8cpp_source.html | 9280 +++++----- NetworkOPs_8h_source.html | 2 +- NoRipple__test_8cpp_source.html | 2 +- Node_8cpp_source.html | 1881 +-- Node_8h_source.html | 32 +- Offer__test_8cpp_source.html | 6 +- Oracle_8cpp_source.html | 6 +- Oracle__test_8cpp_source.html | 6 +- OverlayImpl_8cpp_source.html | 2328 +-- OverlayImpl_8h_source.html | 557 +- OversizeMeta__test_8cpp_source.html | 4 +- PathSet_8h_source.html | 2 +- Path__test_8cpp_source.html | 4 +- PayChanClaim_8cpp_source.html | 4 +- PayChan_8cpp_source.html | 653 +- PayChan__test_8cpp_source.html | 3862 ++--- PayStrand__test_8cpp_source.html | 6 +- PaymentSandbox_8cpp_source.html | 8 +- PaymentSandbox__test_8cpp_source.html | 6 +- PeerImp_8cpp_source.html | 6492 +++---- PeerImp_8h_source.html | 108 +- PeerfinderConfig_8cpp_source.html | 18 +- PerfLog__test_8cpp_source.html | 4 +- PermissionedDomains__test_8cpp_source.html | 4 +- PseudoTx__test_8cpp_source.html | 2 +- RCLConsensus_8cpp_source.html | 10 +- RCLConsensus_8h_source.html | 4 +- RCLValidations__test_8cpp_source.html | 4 +- RPCCall_8cpp_source.html | 4 +- RPCHelpers_8cpp_source.html | 4 +- RPCSub_8cpp_source.html | 335 +- RPCSub_8h_source.html | 2 +- ReducedOffer__test_8cpp_source.html | 2 +- Regression__test_8cpp_source.html | 8 +- RobustTransaction__test_8cpp_source.html | 4 +- Roles__test_8cpp_source.html | 4 +- SHAMapStoreImp_8cpp_source.html | 2 +- SHAMapStore__test_8cpp_source.html | 6 +- SQLiteDatabase_8cpp_source.html | 34 +- STNumber__test_8cpp_source.html | 8 +- STParsedJSON_8cpp_source.html | 2 +- ServerHandler_8cpp_source.html | 4 +- ServerInfo__test_8cpp_source.html | 4 +- ServerStatus__test_8cpp_source.html | 4 +- SetTrust__test_8cpp_source.html | 6 +- SignFor_8cpp_source.html | 2 +- SignHandler_8cpp_source.html | 2 +- Simulate_8cpp_source.html | 2 +- Simulate__test_8cpp_source.html | 12 +- SkipList__test_8cpp_source.html | 2 +- Status_8cpp_source.html | 2 +- StoreSqdb_8h_source.html | 8 +- Submit_8cpp_source.html | 2 +- Subscribe_8cpp_source.html | 2 +- Subscribe__test_8cpp_source.html | 6 +- TestHelpers_8cpp_source.html | 6 +- TheoreticalQuality__test_8cpp_source.html | 2 +- Ticket__test_8cpp_source.html | 2 +- TrafficCount_8cpp_source.html | 328 +- TrafficCount_8h_source.html | 664 +- TransactionEntry__test_8cpp_source.html | 2 +- TransactionMaster_8cpp_source.html | 277 +- TransactionMaster_8h_source.html | 59 +- TransactionSign_8cpp_source.html | 4 +- Transaction_8cpp_source.html | 358 +- Transaction_8h_source.html | 556 +- Transaction__ordering__test_8cpp_source.html | 6 +- Transaction__test_8cpp_source.html | 780 +- Transactor_8cpp_source.html | 16 +- TxQ__test_8cpp_source.html | 14 +- Tx_8cpp_source.html | 358 +- ValidatorInfo_8cpp_source.html | 8 +- ValidatorInfo__test_8cpp_source.html | 2 +- ValidatorKeys_8cpp_source.html | 4 +- ValidatorKeys__test_8cpp_source.html | 2 +- ValidatorList_8cpp_source.html | 10 +- ValidatorList__test_8cpp_source.html | 12 +- ValidatorRPC__test_8cpp_source.html | 2 +- ValidatorSite__test_8cpp_source.html | 4 +- View_8cpp_source.html | 26 +- View_8h_source.html | 18 +- View__test_8cpp_source.html | 4 +- WSClient__test_8cpp_source.html | 2 +- Wallet_8cpp_source.html | 4 +- XChain__test_8cpp_source.html | 6 +- annotated.html | 1209 +- antithesis__sdk_8h_source.html | 2 +- apply__test_8cpp_source.html | 4 +- ...d__associative__container__test__base.html | 2 +- classbeast_1_1unit__test_1_1suite.html | 4 +- classes.html | 4 +- classripple_1_1ApplyContext.html | 4 +- classripple_1_1ApplyViewImpl.html | 4 +- classripple_1_1ConnectAttempt.html | 2 +- ...sripple_1_1ConnectAttempt__coll__graph.map | 2 +- ...sripple_1_1ConnectAttempt__coll__graph.md5 | 2 +- classripple_1_1EscrowCancel.html | 4 +- classripple_1_1EscrowCreate.html | 8 +- classripple_1_1EscrowFinish.html | 8 +- ...ripple_1_1InvariantChecker__PROTOTYPE.html | 2 +- ..._1LedgerRPC__XChain__test__coll__graph.md5 | 1 - ..._1LedgerRPC__XChain__test__coll__graph.png | Bin 341136 -> 0 bytes ...edgerRPC__XChain__test__inherit__graph.md5 | 1 - ...edgerRPC__XChain__test__inherit__graph.png | Bin 8610 -> 0 bytes ...ripple_1_1LedgerRPC__test__coll__graph.md5 | 1 - ...ripple_1_1LedgerRPC__test__coll__graph.png | Bin 68190 -> 0 bytes ...ple_1_1LedgerRPC__test__inherit__graph.map | 4 - ...ple_1_1LedgerRPC__test__inherit__graph.md5 | 1 - ...ple_1_1LedgerRPC__test__inherit__graph.png | Bin 3722 -> 0 bytes classripple_1_1ManifestCache.html | 20 +- classripple_1_1Message-members.html | 2 +- classripple_1_1Message.html | 12 +- classripple_1_1NetworkOPsImp.html | 284 +- ...e_1_1NetworkOPsImp_1_1StateAccounting.html | 26 +- ...1_1NetworkOPsImp_1_1TransactionStatus.html | 31 +- ...sImp_1_1TransactionStatus__coll__graph.map | 73 +- ...sImp_1_1TransactionStatus__coll__graph.md5 | 2 +- ...sImp_1_1TransactionStatus__coll__graph.png | Bin 231249 -> 244820 bytes classripple_1_1NetworkOPsImp__coll__graph.md5 | 2 +- classripple_1_1NodeStore_1_1TestBase.html | 2 +- classripple_1_1OverlayImpl-members.html | 65 +- classripple_1_1OverlayImpl.html | 242 +- classripple_1_1OverlayImpl_1_1Child.html | 8 +- ...e_1_1OverlayImpl_1_1Child__coll__graph.map | 4 +- ...e_1_1OverlayImpl_1_1Child__coll__graph.md5 | 2 +- ...e_1_1OverlayImpl_1_1Child__coll__graph.png | Bin 537090 -> 536906 bytes classripple_1_1OverlayImpl__coll__graph.map | 94 +- classripple_1_1OverlayImpl__coll__graph.md5 | 2 +- classripple_1_1OverlayImpl__coll__graph.png | Bin 526986 -> 534307 bytes classripple_1_1PayChanClaim.html | 6 +- classripple_1_1PayChanFund.html | 6 +- ...le_1_1PeerFinder_1_1StoreSqdb-members.html | 2 +- classripple_1_1PeerFinder_1_1StoreSqdb.html | 10 +- classripple_1_1PeerImp-members.html | 2 +- classripple_1_1PeerImp.html | 162 +- classripple_1_1PeerImp_1_1Metrics.html | 6 +- classripple_1_1PeerImp__coll__graph.map | 2 +- classripple_1_1PeerImp__coll__graph.md5 | 2 +- classripple_1_1RCLConsensus_1_1Adaptor.html | 2 +- classripple_1_1RPCSub.html | 2 +- classripple_1_1RPCSubImp-members.html | 91 +- classripple_1_1RPCSubImp.html | 69 +- classripple_1_1TestSuite.html | 2 +- classripple_1_1TrafficCount-members.html | 19 +- classripple_1_1TrafficCount.html | 159 +- ...1TrafficCount_1_1TrafficStats-members.html | 4 +- ...ipple_1_1TrafficCount_1_1TrafficStats.html | 48 +- ...fficCount_1_1TrafficStats__coll__graph.map | 8 +- ...fficCount_1_1TrafficStats__coll__graph.md5 | 2 +- ...fficCount_1_1TrafficStats__coll__graph.png | Bin 9087 -> 14692 bytes classripple_1_1TrafficCount__coll__graph.map | 12 +- classripple_1_1TrafficCount__coll__graph.md5 | 2 +- classripple_1_1TrafficCount__coll__graph.png | Bin 17335 -> 23221 bytes classripple_1_1Transaction-members.html | 12 +- classripple_1_1Transaction.html | 157 +- classripple_1_1TransactionMaster-members.html | 2 +- classripple_1_1TransactionMaster.html | 40 +- classripple_1_1Transaction__coll__graph.map | 71 +- classripple_1_1Transaction__coll__graph.md5 | 2 +- classripple_1_1Transaction__coll__graph.png | Bin 218691 -> 223898 bytes classripple_1_1Transaction__test.html | 10 +- classripple_1_1TransfersNotFrozen.html | 6 +- classripple_1_1detail_1_1ApplyStateTable.html | 4 +- ...detail_1_1BasicFullBelowCache-members.html | 2 +- ...pple_1_1detail_1_1BasicFullBelowCache.html | 14 +- classripple_1_1test_1_1AccountInfo__test.html | 2 +- ...le_1_1test_1_1DepositAuthorized__test.html | 8 +- ..._1_1LedgerEntry__XChain__test-members.html | 16 +- ..._1_1test_1_1LedgerEntry__XChain__test.html | 212 +- ...LedgerEntry__XChain__test__coll__graph.map | 4 +- ...LedgerEntry__XChain__test__coll__graph.md5 | 1 + ...LedgerEntry__XChain__test__coll__graph.png | Bin 0 -> 340035 bytes ...gerEntry__XChain__test__inherit__graph.map | 4 +- ...gerEntry__XChain__test__inherit__graph.md5 | 1 + ...gerEntry__XChain__test__inherit__graph.png | Bin 0 -> 8755 bytes ..._1_1test_1_1LedgerEntry__test-members.html | 62 +- ...ssripple_1_1test_1_1LedgerEntry__test.html | 594 +- ...test_1_1LedgerEntry__test__coll__graph.map | 20 + ...test_1_1LedgerEntry__test__coll__graph.md5 | 1 + ...test_1_1LedgerEntry__test__coll__graph.png | Bin 0 -> 68606 bytes ...t_1_1LedgerEntry__test__inherit__graph.map | 4 + ...t_1_1LedgerEntry__test__inherit__graph.md5 | 1 + ...t_1_1LedgerEntry__test__inherit__graph.png | Bin 0 -> 3773 bytes ...le_1_1test_1_1LedgerRPC__test-members.html | 130 + classripple_1_1test_1_1LedgerRPC__test.html | 1477 ++ ..._1test_1_1LedgerRPC__test__coll__graph.map | 10 +- ..._1test_1_1LedgerRPC__test__coll__graph.md5 | 1 + ..._1test_1_1LedgerRPC__test__coll__graph.png | Bin 0 -> 68696 bytes ...est_1_1LedgerRPC__test__inherit__graph.map | 4 + ...est_1_1LedgerRPC__test__inherit__graph.md5 | 1 + ...est_1_1LedgerRPC__test__inherit__graph.png | Bin 0 -> 3860 bytes classripple_1_1test_1_1TestOutputSuite.html | 2 +- classripple_1_1test_1_1jtx_1_1AMMTest.html | 2 +- ...sripple_1_1test_1_1jtx_1_1AMMTestBase.html | 2 +- ...sripple_1_1test_1_1jtx_1_1Env-members.html | 2 +- classripple_1_1test_1_1jtx_1_1Env.html | 98 +- ...ipple_1_1test_1_1multi__runner__child.html | 10 +- ...1test_1_1traffic__count__test-members.html | 122 + ...ipple_1_1test_1_1traffic__count__test.html | 1204 ++ ...t_1_1traffic__count__test__coll__graph.map | 20 + ...t_1_1traffic__count__test__coll__graph.md5 | 1 + ...t_1_1traffic__count__test__coll__graph.png | Bin 0 -> 69935 bytes ..._1traffic__count__test__inherit__graph.map | 4 + ..._1traffic__count__test__inherit__graph.md5 | 1 + ..._1traffic__count__test__inherit__graph.png | Bin 0 -> 4070 bytes ...duce__relay__test_1_1PeerTest-members.html | 2 +- ..._1tx__reduce__relay__test_1_1PeerTest.html | 160 +- ...__relay__test_1_1PeerTest__coll__graph.map | 2 +- ...__relay__test_1_1PeerTest__coll__graph.md5 | 2 +- compression__test_8cpp_source.html | 2 +- credentials_8h_source.html | 8 +- ...8cpp_source.html => creds_8cpp_source.html | 14 +- did_8h_source.html | 4 +- did_8cpp_source.html => dids_8cpp_source.html | 8 +- dir_202f05fcc82d395fac3f43e301f5a49f.html | 4 +- dir_9e5c3433ad783ee5f9e7b66541a4db0e.html | 2 + dir_b6e1af3af959840d44bdaef0b719b3f9.html | 6 +- dir_c80815dbdc2122d1b16ef343a4cf0d47.html | 2 +- dir_d1feb4416989cd829d5b1945d720f449.html | 2 + functions_a.html | 8 +- functions_c.html | 36 +- functions_d.html | 4 +- functions_e.html | 31 +- functions_eval_c.html | 2 +- functions_eval_d.html | 2 +- functions_eval_e.html | 1 - functions_eval_p.html | 2 + functions_eval_s.html | 2 + functions_eval_t.html | 2 + functions_eval_v.html | 2 + functions_f.html | 8 +- functions_func.html | 2 +- functions_func_c.html | 12 +- functions_func_d.html | 2 +- functions_func_e.html | 4 +- functions_func_i.html | 2 +- functions_func_l.html | 4 +- functions_func_m.html | 18 +- functions_func_o.html | 4 +- functions_func_r.html | 15 +- functions_func_s.html | 24 +- functions_func_t.html | 83 +- functions_func_v.html | 14 +- functions_i.html | 30 +- functions_l.html | 10 +- functions_m.html | 19 +- functions_n.html | 14 +- functions_p.html | 26 +- functions_q.html | 3 +- functions_r.html | 9 +- functions_s.html | 61 +- functions_t.html | 90 +- functions_u.html | 2 +- functions_v.html | 16 +- functions_vars_c.html | 2 +- functions_vars_e.html | 4 + functions_vars_i.html | 2 +- functions_vars_m.html | 7 +- functions_vars_n.html | 2 +- functions_vars_s.html | 2 +- functions_vars_t.html | 2 +- functions_w.html | 6 +- hierarchy.html | 1019 +- inherit_graph_1000.map | 4 +- inherit_graph_1000.md5 | 2 +- inherit_graph_1000.png | Bin 11456 -> 8391 bytes inherit_graph_1001.map | 4 +- inherit_graph_1001.md5 | 2 +- inherit_graph_1001.png | Bin 7948 -> 11456 bytes inherit_graph_1002.map | 4 +- inherit_graph_1002.md5 | 2 +- inherit_graph_1002.png | Bin 6714 -> 7948 bytes inherit_graph_1003.map | 8 +- inherit_graph_1003.md5 | 2 +- inherit_graph_1003.png | Bin 27916 -> 6714 bytes inherit_graph_1004.map | 12 +- inherit_graph_1004.md5 | 2 +- inherit_graph_1004.png | Bin 15693 -> 27916 bytes inherit_graph_1005.map | 7 +- inherit_graph_1005.md5 | 2 +- inherit_graph_1005.png | Bin 3207 -> 15693 bytes inherit_graph_1006.map | 3 +- inherit_graph_1006.md5 | 2 +- inherit_graph_1006.png | Bin 4581 -> 3207 bytes inherit_graph_1007.map | 4 +- inherit_graph_1007.md5 | 2 +- inherit_graph_1007.png | Bin 3834 -> 4581 bytes inherit_graph_1008.map | 22 +- inherit_graph_1008.md5 | 2 +- inherit_graph_1008.png | Bin 135659 -> 3834 bytes inherit_graph_1009.map | 22 +- inherit_graph_1009.md5 | 2 +- inherit_graph_1009.png | Bin 4173 -> 135659 bytes inherit_graph_1010.map | 18 +- inherit_graph_1010.md5 | 2 +- inherit_graph_1010.png | Bin 65131 -> 4173 bytes inherit_graph_1011.map | 18 +- inherit_graph_1011.md5 | 2 +- inherit_graph_1011.png | Bin 4970 -> 65131 bytes inherit_graph_1012.map | 5 +- inherit_graph_1012.md5 | 2 +- inherit_graph_1012.png | Bin 7463 -> 4970 bytes inherit_graph_1013.map | 5 + inherit_graph_1013.md5 | 1 + inherit_graph_1013.png | Bin 0 -> 7463 bytes inherit_graph_879.map | 2 +- inherit_graph_879.md5 | 2 +- inherit_graph_879.png | Bin 2751 -> 2566 bytes inherit_graph_880.map | 2 +- inherit_graph_880.md5 | 2 +- inherit_graph_880.png | Bin 2595 -> 2751 bytes inherit_graph_881.map | 2 +- inherit_graph_881.md5 | 2 +- inherit_graph_881.png | Bin 2832 -> 2595 bytes inherit_graph_882.map | 2 +- inherit_graph_882.md5 | 2 +- inherit_graph_882.png | Bin 1760 -> 2832 bytes inherit_graph_883.map | 2 +- inherit_graph_883.md5 | 2 +- inherit_graph_883.png | Bin 2016 -> 1760 bytes inherit_graph_884.map | 2 +- inherit_graph_884.md5 | 2 +- inherit_graph_884.png | Bin 3019 -> 2016 bytes inherit_graph_885.map | 2 +- inherit_graph_885.md5 | 2 +- inherit_graph_885.png | Bin 2286 -> 3019 bytes inherit_graph_886.map | 2 +- inherit_graph_886.md5 | 2 +- inherit_graph_886.png | Bin 3095 -> 2286 bytes inherit_graph_887.map | 2 +- inherit_graph_887.md5 | 2 +- inherit_graph_887.png | Bin 3298 -> 3095 bytes inherit_graph_888.map | 2 +- inherit_graph_888.md5 | 2 +- inherit_graph_888.png | Bin 2735 -> 3298 bytes inherit_graph_889.map | 2 +- inherit_graph_889.md5 | 2 +- inherit_graph_889.png | Bin 3823 -> 2735 bytes inherit_graph_890.map | 2 +- inherit_graph_890.md5 | 2 +- inherit_graph_890.png | Bin 3116 -> 3823 bytes inherit_graph_891.map | 2 +- inherit_graph_891.md5 | 2 +- inherit_graph_891.png | Bin 2755 -> 3116 bytes inherit_graph_892.map | 3 +- inherit_graph_892.md5 | 2 +- inherit_graph_892.png | Bin 4619 -> 2755 bytes inherit_graph_893.map | 4 +- inherit_graph_893.md5 | 2 +- inherit_graph_893.png | Bin 4008 -> 4619 bytes inherit_graph_894.map | 3 +- inherit_graph_894.md5 | 2 +- inherit_graph_894.png | Bin 3217 -> 4008 bytes inherit_graph_895.map | 2 +- inherit_graph_895.md5 | 2 +- inherit_graph_895.png | Bin 2672 -> 3217 bytes inherit_graph_896.map | 2 +- inherit_graph_896.md5 | 2 +- inherit_graph_896.png | Bin 2655 -> 2672 bytes inherit_graph_897.map | 2 +- inherit_graph_897.md5 | 2 +- inherit_graph_897.png | Bin 2201 -> 2655 bytes inherit_graph_898.map | 2 +- inherit_graph_898.md5 | 2 +- inherit_graph_898.png | Bin 2507 -> 2201 bytes inherit_graph_899.map | 2 +- inherit_graph_899.md5 | 2 +- inherit_graph_899.png | Bin 3156 -> 2507 bytes inherit_graph_900.map | 2 +- inherit_graph_900.md5 | 2 +- inherit_graph_901.map | 5 +- inherit_graph_901.md5 | 2 +- inherit_graph_901.png | Bin 10427 -> 3156 bytes inherit_graph_902.map | 6 +- inherit_graph_902.md5 | 2 +- inherit_graph_902.png | Bin 4720 -> 10427 bytes inherit_graph_903.map | 4 +- inherit_graph_903.md5 | 2 +- inherit_graph_903.png | Bin 4177 -> 4720 bytes inherit_graph_904.map | 3 +- inherit_graph_904.md5 | 2 +- inherit_graph_904.png | Bin 3446 -> 4177 bytes inherit_graph_905.map | 2 +- inherit_graph_905.md5 | 2 +- inherit_graph_905.png | Bin 1868 -> 3446 bytes inherit_graph_906.map | 2 +- inherit_graph_906.md5 | 2 +- inherit_graph_906.png | Bin 2505 -> 1868 bytes inherit_graph_907.map | 2 +- inherit_graph_907.md5 | 2 +- inherit_graph_907.png | Bin 2804 -> 2505 bytes inherit_graph_908.map | 2 +- inherit_graph_908.md5 | 2 +- inherit_graph_908.png | Bin 1957 -> 2804 bytes inherit_graph_909.map | 2 +- inherit_graph_909.md5 | 2 +- inherit_graph_909.png | Bin 2588 -> 1957 bytes inherit_graph_910.map | 2 +- inherit_graph_910.md5 | 2 +- inherit_graph_910.png | Bin 2058 -> 2588 bytes inherit_graph_911.map | 2 +- inherit_graph_911.md5 | 2 +- inherit_graph_911.png | Bin 1880 -> 2058 bytes inherit_graph_912.map | 58 +- inherit_graph_912.md5 | 2 +- inherit_graph_912.png | Bin 352297 -> 1880 bytes inherit_graph_913.map | 58 +- inherit_graph_913.md5 | 2 +- inherit_graph_913.png | Bin 1925 -> 352297 bytes inherit_graph_914.map | 2 +- inherit_graph_914.md5 | 2 +- inherit_graph_914.png | Bin 2932 -> 1925 bytes inherit_graph_915.map | 2 +- inherit_graph_915.md5 | 2 +- inherit_graph_915.png | Bin 2794 -> 2932 bytes inherit_graph_916.map | 2 +- inherit_graph_916.md5 | 2 +- inherit_graph_916.png | Bin 1853 -> 2794 bytes inherit_graph_917.map | 2 +- inherit_graph_917.md5 | 2 +- inherit_graph_917.png | Bin 1693 -> 1853 bytes inherit_graph_918.map | 2 +- inherit_graph_918.md5 | 2 +- inherit_graph_918.png | Bin 2596 -> 1693 bytes inherit_graph_919.map | 2 +- inherit_graph_919.md5 | 2 +- inherit_graph_919.png | Bin 1493 -> 2596 bytes inherit_graph_920.map | 2 +- inherit_graph_920.md5 | 2 +- inherit_graph_920.png | Bin 1988 -> 1493 bytes inherit_graph_921.map | 2 +- inherit_graph_921.md5 | 2 +- inherit_graph_921.png | Bin 1370 -> 1988 bytes inherit_graph_922.map | 2 +- inherit_graph_922.md5 | 2 +- inherit_graph_922.png | Bin 1938 -> 1370 bytes inherit_graph_923.map | 2 +- inherit_graph_923.md5 | 2 +- inherit_graph_923.png | Bin 1263 -> 1938 bytes inherit_graph_924.map | 2 +- inherit_graph_924.md5 | 2 +- inherit_graph_924.png | Bin 2018 -> 1263 bytes inherit_graph_925.map | 2 +- inherit_graph_925.md5 | 2 +- inherit_graph_925.png | Bin 1903 -> 2018 bytes inherit_graph_926.map | 2 +- inherit_graph_926.md5 | 2 +- inherit_graph_926.png | Bin 2751 -> 1903 bytes inherit_graph_927.map | 2 +- inherit_graph_927.md5 | 2 +- inherit_graph_927.png | Bin 1844 -> 2751 bytes inherit_graph_928.map | 2 +- inherit_graph_928.md5 | 2 +- inherit_graph_928.png | Bin 1834 -> 1844 bytes inherit_graph_929.map | 2 +- inherit_graph_929.md5 | 2 +- inherit_graph_929.png | Bin 2272 -> 1834 bytes inherit_graph_930.map | 2 +- inherit_graph_930.md5 | 2 +- inherit_graph_930.png | Bin 1668 -> 2272 bytes inherit_graph_931.map | 2 +- inherit_graph_931.md5 | 2 +- inherit_graph_931.png | Bin 1877 -> 1668 bytes inherit_graph_932.map | 2 +- inherit_graph_932.md5 | 2 +- inherit_graph_932.png | Bin 1986 -> 1877 bytes inherit_graph_933.map | 2 +- inherit_graph_933.md5 | 2 +- inherit_graph_933.png | Bin 1463 -> 1986 bytes inherit_graph_934.map | 3 +- inherit_graph_934.md5 | 2 +- inherit_graph_934.png | Bin 4824 -> 1463 bytes inherit_graph_935.map | 3 +- inherit_graph_935.md5 | 2 +- inherit_graph_935.png | Bin 1978 -> 4824 bytes inherit_graph_936.map | 2 +- inherit_graph_936.md5 | 2 +- inherit_graph_936.png | Bin 2648 -> 1978 bytes inherit_graph_937.map | 2 +- inherit_graph_937.md5 | 2 +- inherit_graph_937.png | Bin 1783 -> 2648 bytes inherit_graph_938.map | 2 +- inherit_graph_938.md5 | 2 +- inherit_graph_938.png | Bin 2036 -> 1783 bytes inherit_graph_939.map | 2 +- inherit_graph_939.md5 | 2 +- inherit_graph_939.png | Bin 2647 -> 2036 bytes inherit_graph_94.map | 556 +- inherit_graph_94.md5 | 2 +- inherit_graph_94.png | Bin 2280935 -> 2308063 bytes inherit_graph_940.map | 2 +- inherit_graph_940.md5 | 2 +- inherit_graph_940.png | Bin 3359 -> 2647 bytes inherit_graph_941.map | 2 +- inherit_graph_941.md5 | 2 +- inherit_graph_941.png | Bin 3178 -> 3359 bytes inherit_graph_942.map | 2 +- inherit_graph_942.md5 | 2 +- inherit_graph_942.png | Bin 3240 -> 3178 bytes inherit_graph_943.map | 2 +- inherit_graph_943.md5 | 2 +- inherit_graph_943.png | Bin 1979 -> 3240 bytes inherit_graph_944.map | 2 +- inherit_graph_944.md5 | 2 +- inherit_graph_944.png | Bin 2045 -> 1979 bytes inherit_graph_945.map | 2 +- inherit_graph_945.md5 | 2 +- inherit_graph_945.png | Bin 2332 -> 2045 bytes inherit_graph_946.map | 2 +- inherit_graph_946.md5 | 2 +- inherit_graph_946.png | Bin 1760 -> 2332 bytes inherit_graph_947.map | 2 +- inherit_graph_947.md5 | 2 +- inherit_graph_947.png | Bin 2869 -> 1760 bytes inherit_graph_948.map | 2 +- inherit_graph_948.md5 | 2 +- inherit_graph_948.png | Bin 2289 -> 2869 bytes inherit_graph_949.map | 2 +- inherit_graph_949.md5 | 2 +- inherit_graph_949.png | Bin 2831 -> 2289 bytes inherit_graph_950.map | 2 +- inherit_graph_950.md5 | 2 +- inherit_graph_950.png | Bin 2526 -> 2831 bytes inherit_graph_951.map | 2 +- inherit_graph_951.md5 | 2 +- inherit_graph_951.png | Bin 1818 -> 2526 bytes inherit_graph_952.map | 2 +- inherit_graph_952.md5 | 2 +- inherit_graph_952.png | Bin 2014 -> 1818 bytes inherit_graph_953.map | 2 +- inherit_graph_953.md5 | 2 +- inherit_graph_953.png | Bin 2528 -> 2014 bytes inherit_graph_954.map | 2 +- inherit_graph_954.md5 | 2 +- inherit_graph_954.png | Bin 2402 -> 2528 bytes inherit_graph_955.map | 2 +- inherit_graph_955.md5 | 2 +- inherit_graph_955.png | Bin 1844 -> 2402 bytes inherit_graph_956.map | 2 +- inherit_graph_956.md5 | 2 +- inherit_graph_956.png | Bin 2009 -> 1844 bytes inherit_graph_957.map | 2 +- inherit_graph_957.md5 | 2 +- inherit_graph_957.png | Bin 2083 -> 2009 bytes inherit_graph_958.map | 2 +- inherit_graph_958.md5 | 2 +- inherit_graph_958.png | Bin 2575 -> 2083 bytes inherit_graph_959.map | 2 +- inherit_graph_959.md5 | 2 +- inherit_graph_959.png | Bin 2193 -> 2575 bytes inherit_graph_960.map | 2 +- inherit_graph_960.md5 | 2 +- inherit_graph_960.png | Bin 2465 -> 2193 bytes inherit_graph_961.map | 2 +- inherit_graph_961.md5 | 2 +- inherit_graph_961.png | Bin 3109 -> 2465 bytes inherit_graph_962.map | 2 +- inherit_graph_962.md5 | 2 +- inherit_graph_962.png | Bin 2382 -> 3109 bytes inherit_graph_963.map | 2 +- inherit_graph_963.md5 | 2 +- inherit_graph_963.png | Bin 1543 -> 2382 bytes inherit_graph_964.map | 4 +- inherit_graph_964.md5 | 2 +- inherit_graph_964.png | Bin 6535 -> 1543 bytes inherit_graph_965.map | 4 +- inherit_graph_965.md5 | 2 +- inherit_graph_965.png | Bin 2335 -> 6535 bytes inherit_graph_966.map | 3 +- inherit_graph_966.md5 | 2 +- inherit_graph_966.png | Bin 2157 -> 2335 bytes inherit_graph_967.map | 4 +- inherit_graph_967.md5 | 2 +- inherit_graph_967.png | Bin 4529 -> 2157 bytes inherit_graph_968.map | 3 +- inherit_graph_968.md5 | 2 +- inherit_graph_968.png | Bin 3142 -> 4529 bytes inherit_graph_969.map | 3 +- inherit_graph_969.md5 | 2 +- inherit_graph_969.png | Bin 5351 -> 3142 bytes inherit_graph_970.map | 4 +- inherit_graph_970.md5 | 2 +- inherit_graph_970.png | Bin 4492 -> 5351 bytes inherit_graph_971.map | 3 +- inherit_graph_971.md5 | 2 +- inherit_graph_971.png | Bin 2254 -> 4492 bytes inherit_graph_972.map | 2 +- inherit_graph_972.md5 | 2 +- inherit_graph_972.png | Bin 3158 -> 2254 bytes inherit_graph_973.map | 2 +- inherit_graph_973.md5 | 2 +- inherit_graph_973.png | Bin 2686 -> 3158 bytes inherit_graph_974.map | 2 +- inherit_graph_974.md5 | 2 +- inherit_graph_974.png | Bin 3316 -> 2686 bytes inherit_graph_975.map | 2 +- inherit_graph_975.md5 | 2 +- inherit_graph_975.png | Bin 2276 -> 3316 bytes inherit_graph_976.map | 2 +- inherit_graph_976.md5 | 2 +- inherit_graph_976.png | Bin 2177 -> 2276 bytes inherit_graph_977.map | 3 +- inherit_graph_977.md5 | 2 +- inherit_graph_977.png | Bin 3774 -> 2177 bytes inherit_graph_978.map | 7 +- inherit_graph_978.md5 | 2 +- inherit_graph_978.png | Bin 23274 -> 3774 bytes inherit_graph_979.map | 7 +- inherit_graph_979.md5 | 2 +- inherit_graph_979.png | Bin 6025 -> 23274 bytes inherit_graph_980.map | 4 +- inherit_graph_980.md5 | 2 +- inherit_graph_980.png | Bin 3746 -> 6025 bytes inherit_graph_981.map | 5 +- inherit_graph_981.md5 | 2 +- inherit_graph_981.png | Bin 11164 -> 3746 bytes inherit_graph_982.map | 5 +- inherit_graph_982.md5 | 2 +- inherit_graph_982.png | Bin 4571 -> 11164 bytes inherit_graph_983.map | 4 +- inherit_graph_983.md5 | 2 +- inherit_graph_983.png | Bin 4347 -> 4571 bytes inherit_graph_984.map | 20 +- inherit_graph_984.md5 | 2 +- inherit_graph_984.png | Bin 57980 -> 4347 bytes inherit_graph_985.map | 42 +- inherit_graph_985.md5 | 2 +- inherit_graph_985.png | Bin 111738 -> 57980 bytes inherit_graph_986.map | 28 +- inherit_graph_986.md5 | 2 +- inherit_graph_986.png | Bin 12078 -> 111738 bytes inherit_graph_987.map | 7 +- inherit_graph_987.md5 | 2 +- inherit_graph_987.png | Bin 7487 -> 12078 bytes inherit_graph_988.map | 5 +- inherit_graph_988.md5 | 2 +- inherit_graph_988.png | Bin 4332 -> 7487 bytes inherit_graph_989.map | 4 +- inherit_graph_989.md5 | 2 +- inherit_graph_989.png | Bin 5454 -> 4332 bytes inherit_graph_990.map | 12 +- inherit_graph_990.md5 | 2 +- inherit_graph_990.png | Bin 37543 -> 5454 bytes inherit_graph_991.map | 13 +- inherit_graph_991.md5 | 2 +- inherit_graph_991.png | Bin 8135 -> 37543 bytes inherit_graph_992.map | 17 +- inherit_graph_992.md5 | 2 +- inherit_graph_992.png | Bin 43699 -> 8135 bytes inherit_graph_993.map | 20 +- inherit_graph_993.md5 | 2 +- inherit_graph_993.png | Bin 22241 -> 43699 bytes inherit_graph_994.map | 8 +- inherit_graph_994.md5 | 2 +- inherit_graph_994.png | Bin 3127 -> 22241 bytes inherit_graph_995.map | 3 +- inherit_graph_995.md5 | 2 +- inherit_graph_995.png | Bin 2348 -> 3127 bytes inherit_graph_996.map | 4 +- inherit_graph_996.md5 | 2 +- inherit_graph_996.png | Bin 8451 -> 2348 bytes inherit_graph_997.map | 5 +- inherit_graph_997.md5 | 2 +- inherit_graph_997.png | Bin 9534 -> 8451 bytes inherit_graph_998.map | 4 +- inherit_graph_998.md5 | 2 +- inherit_graph_998.png | Bin 9125 -> 9534 bytes inherit_graph_999.map | 4 +- inherit_graph_999.md5 | 2 +- inherit_graph_999.png | Bin 8391 -> 9125 bytes inherits.html | 831 +- ledgerStateFix_8h_source.html | 2 +- ....html => ledgerStateFixes_8cpp_source.html | 6 +- make__Overlay_8h_source.html | 4 +- mpt_8cpp_source.html | 6 +- mpt_8h_source.html | 4 +- multi__runner_8cpp_source.html | 150 +- multi__runner_8h_source.html | 10 +- namespacemembers.html | 2 +- namespacemembers_b.html | 12 +- namespacemembers_c.html | 4 +- namespacemembers_d.html | 10 +- namespacemembers_e.html | 2 +- namespacemembers_eval_b.html | 8 +- namespacemembers_eval_c.html | 4 +- namespacemembers_eval_d.html | 8 +- namespacemembers_eval_h.html | 2 +- namespacemembers_eval_m.html | 8 +- namespacemembers_eval_o.html | 4 +- namespacemembers_eval_s.html | 12 +- namespacemembers_eval_t.html | 2 +- namespacemembers_eval_u.html | 2 +- namespacemembers_func.html | 2 +- namespacemembers_func_b.html | 6 +- namespacemembers_func_d.html | 2 +- namespacemembers_func_e.html | 2 +- namespacemembers_h.html | 2 +- namespacemembers_i.html | 4 +- namespacemembers_m.html | 8 +- namespacemembers_o.html | 4 +- namespacemembers_s.html | 12 +- namespacemembers_t.html | 5 +- namespacemembers_u.html | 2 +- namespacemembers_vars_t.html | 1 + namespaceripple.html | 1506 +- namespaceripple_1_1CrawlOptions.html | 30 +- namespaceripple_1_1NodeStore.html | 14 +- namespaceripple_1_1PeerFinder_1_1Tuning.html | 76 +- namespaceripple_1_1RPC.html | 32 +- namespaceripple_1_1RPC_1_1detail.html | 6 +- namespaceripple_1_1Tuning.html | 48 +- namespaceripple_1_1detail.html | 44 +- namespaceripple_1_1test.html | 348 +- ...eripple_1_1test_1_1jtx_1_1credentials.html | 10 +- namespaceripple_1_1test_1_1jtx_1_1did.html | 6 +- ...pple_1_1test_1_1jtx_1_1ledgerStateFix.html | 2 +- namespaces.html | 1217 +- owners_8cpp_source.html | 2 +- paths_8cpp_source.html | 4 +- permissioned__domains_8cpp_source.html | 2 +- rpc_8h_source.html | 2 +- search/all_10.js | 343 +- search/all_11.js | 829 +- search/all_12.js | 19 +- search/all_13.js | 1017 +- search/all_14.js | 2695 +-- search/all_15.js | 3472 ++-- search/all_16.js | 525 +- search/all_17.js | 533 +- search/all_18.js | 79 +- search/all_1b.js | 22 +- search/all_1c.js | 237 +- search/all_2.js | 1420 +- search/all_3.js | 339 +- search/all_4.js | 1579 +- search/all_5.js | 277 +- search/all_6.js | 459 +- search/all_7.js | 752 +- search/all_8.js | 4 +- search/all_9.js | 340 +- search/all_a.js | 1635 +- search/all_b.js | 6 +- search/all_d.js | 439 +- search/all_e.js | 1421 +- search/all_f.js | 395 +- search/classes_0.js | 189 +- search/classes_13.js | 236 +- search/classes_14.js | 170 +- search/classes_15.js | 127 +- search/classes_b.js | 225 +- search/classes_e.js | 57 +- search/enumvalues_1.js | 25 +- search/enumvalues_10.js | 18 +- search/enumvalues_13.js | 70 +- search/enumvalues_14.js | 64 +- search/enumvalues_15.js | 2 +- search/enumvalues_16.js | 8 +- search/enumvalues_2.js | 8 +- search/enumvalues_3.js | 10 +- search/enumvalues_4.js | 10 +- search/enumvalues_5.js | 13 +- search/enumvalues_6.js | 5 +- search/enumvalues_8.js | 2 +- search/enumvalues_d.js | 8 +- search/enumvalues_e.js | 10 +- search/enumvalues_f.js | 10 +- search/functions_1.js | 273 +- search/functions_12.js | 314 +- search/functions_13.js | 1106 +- search/functions_14.js | 2403 +-- search/functions_15.js | 205 +- search/functions_16.js | 120 +- search/functions_17.js | 4 +- search/functions_1a.js | 4 +- search/functions_1b.js | 237 +- search/functions_2.js | 18 +- search/functions_3.js | 831 +- search/functions_4.js | 14 +- search/functions_5.js | 18 +- search/functions_6.js | 236 +- search/functions_7.js | 27 +- search/functions_8.js | 4 +- search/functions_9.js | 173 +- search/functions_a.js | 6 +- search/functions_c.js | 4 +- search/functions_d.js | 387 +- search/functions_f.js | 56 +- search/typedefs_2.js | 53 +- search/typedefs_8.js | 53 +- search/variables_12.js | 2 +- search/variables_13.js | 15 +- search/variables_16.js | 6 +- search/variables_2.js | 2 +- search/variables_4.js | 56 +- search/variables_8.js | 16 +- search/variables_c.js | 461 +- search/variables_d.js | 18 +- ...d_2app_2tx_2detail_2PayChan_8h_source.html | 12 +- src_2xrpld_2nodestore_2Types_8h_source.html | 10 +- ...ld_2overlay_2detail_2Tuning_8h_source.html | 40 +- ..._2peerfinder_2detail_2Logic_8h_source.html | 4 +- ...2peerfinder_2detail_2Tuning_8h_source.html | 48 +- structripple_1_1Manifest.html | 12 +- ..._1_1NetworkOPsImp_1_1ServerFeeSummary.html | 16 +- ...Imp_1_1StateAccounting_1_1CounterData.html | 10 +- ...OPsImp_1_1StateAccounting_1_1Counters.html | 6 +- structripple_1_1NetworkOPsImp_1_1Stats.html | 26 +- ...tworkOPsImp_1_1SubAccountHistoryIndex.html | 18 +- ...etworkOPsImp_1_1SubAccountHistoryInfo.html | 6 +- ...rkOPsImp_1_1SubAccountHistoryInfoWeak.html | 6 +- ...ipple_1_1OverlayImpl_1_1Stats-members.html | 4 +- structripple_1_1OverlayImpl_1_1Stats.html | 56 +- ...e_1_1OverlayImpl_1_1Stats__coll__graph.map | 24 +- ...e_1_1OverlayImpl_1_1Stats__coll__graph.md5 | 2 +- ...e_1_1OverlayImpl_1_1Stats__coll__graph.png | Bin 41728 -> 54106 bytes structripple_1_1OverlayImpl_1_1Timer.html | 12 +- ...e_1_1OverlayImpl_1_1Timer__coll__graph.map | 4 +- ...e_1_1OverlayImpl_1_1Timer__coll__graph.md5 | 2 +- ...e_1_1OverlayImpl_1_1Timer__coll__graph.png | Bin 529863 -> 529755 bytes ...1OverlayImpl_1_1TrafficGauges-members.html | 3 +- ...ipple_1_1OverlayImpl_1_1TrafficGauges.html | 52 +- ...rlayImpl_1_1TrafficGauges__coll__graph.map | 12 +- ...rlayImpl_1_1TrafficGauges__coll__graph.md5 | 2 +- ...rlayImpl_1_1TrafficGauges__coll__graph.png | Bin 16721 -> 22082 bytes ..._1_1Transaction_1_1CurrentLedgerState.html | 12 +- structripple_1_1Transaction_1_1Locator.html | 12 +- ...ripple_1_1Transaction_1_1SubmitResult.html | 14 +- structripple_1_1TxResult.html | 41 +- structripple_1_1TxResult__coll__graph.map | 81 +- structripple_1_1TxResult__coll__graph.md5 | 2 +- structripple_1_1TxResult__coll__graph.png | Bin 247507 -> 254251 bytes structripple_1_1test_1_1AMM__test.html | 70 +- structripple_1_1test_1_1PayChan__test.html | 42 +- ...1_1test_1_1jtx_1_1XChainBridgeObjects.html | 10 +- ...1_1XChainBridgeObjects__inherit__graph.map | 10 +- ...1_1XChainBridgeObjects__inherit__graph.md5 | 2 +- ...1_1XChainBridgeObjects__inherit__graph.png | Bin 15671 -> 15805 bytes ...ffic__count__test_1_1TestCase-members.html | 96 + ...t_1_1traffic__count__test_1_1TestCase.html | 251 + ...__count__test_1_1TestCase__coll__graph.map | 6 + ...__count__test_1_1TestCase__coll__graph.md5 | 1 + ...__count__test_1_1TestCase__coll__graph.png | Bin 0 -> 12046 bytes token_8cpp_source.html | 2 +- traffic__count__test_8cpp_source.html | 273 + tx__reduce__relay__test_8cpp_source.html | 2 +- utility_8cpp_source.html | 2 +- 938 files changed, 51811 insertions(+), 47481 deletions(-) rename rpc_2handlers_2Manifest_8cpp_source.html => DoManifest_8cpp_source.html (95%) create mode 100644 LedgerEntry__test_8cpp_source.html rename app_2misc_2detail_2Manifest_8cpp_source.html => Manifest_8cpp_source.html (98%) delete mode 100644 classripple_1_1LedgerRPC__XChain__test__coll__graph.md5 delete mode 100644 classripple_1_1LedgerRPC__XChain__test__coll__graph.png delete mode 100644 classripple_1_1LedgerRPC__XChain__test__inherit__graph.md5 delete mode 100644 classripple_1_1LedgerRPC__XChain__test__inherit__graph.png delete mode 100644 classripple_1_1LedgerRPC__test__coll__graph.md5 delete mode 100644 classripple_1_1LedgerRPC__test__coll__graph.png delete mode 100644 classripple_1_1LedgerRPC__test__inherit__graph.map delete mode 100644 classripple_1_1LedgerRPC__test__inherit__graph.md5 delete mode 100644 classripple_1_1LedgerRPC__test__inherit__graph.png rename classripple_1_1LedgerRPC__XChain__test-members.html => classripple_1_1test_1_1LedgerEntry__XChain__test-members.html (93%) rename classripple_1_1LedgerRPC__XChain__test.html => classripple_1_1test_1_1LedgerEntry__XChain__test.html (85%) rename classripple_1_1LedgerRPC__XChain__test__coll__graph.map => classripple_1_1test_1_1LedgerEntry__XChain__test__coll__graph.map (97%) create mode 100644 classripple_1_1test_1_1LedgerEntry__XChain__test__coll__graph.md5 create mode 100644 classripple_1_1test_1_1LedgerEntry__XChain__test__coll__graph.png rename classripple_1_1LedgerRPC__XChain__test__inherit__graph.map => classripple_1_1test_1_1LedgerEntry__XChain__test__inherit__graph.map (62%) create mode 100644 classripple_1_1test_1_1LedgerEntry__XChain__test__inherit__graph.md5 create mode 100644 classripple_1_1test_1_1LedgerEntry__XChain__test__inherit__graph.png rename classripple_1_1LedgerRPC__test-members.html => classripple_1_1test_1_1LedgerEntry__test-members.html (57%) rename classripple_1_1LedgerRPC__test.html => classripple_1_1test_1_1LedgerEntry__test.html (69%) create mode 100644 classripple_1_1test_1_1LedgerEntry__test__coll__graph.map create mode 100644 classripple_1_1test_1_1LedgerEntry__test__coll__graph.md5 create mode 100644 classripple_1_1test_1_1LedgerEntry__test__coll__graph.png create mode 100644 classripple_1_1test_1_1LedgerEntry__test__inherit__graph.map create mode 100644 classripple_1_1test_1_1LedgerEntry__test__inherit__graph.md5 create mode 100644 classripple_1_1test_1_1LedgerEntry__test__inherit__graph.png create mode 100644 classripple_1_1test_1_1LedgerRPC__test-members.html create mode 100644 classripple_1_1test_1_1LedgerRPC__test.html rename classripple_1_1LedgerRPC__test__coll__graph.map => classripple_1_1test_1_1LedgerRPC__test__coll__graph.map (87%) create mode 100644 classripple_1_1test_1_1LedgerRPC__test__coll__graph.md5 create mode 100644 classripple_1_1test_1_1LedgerRPC__test__coll__graph.png create mode 100644 classripple_1_1test_1_1LedgerRPC__test__inherit__graph.map create mode 100644 classripple_1_1test_1_1LedgerRPC__test__inherit__graph.md5 create mode 100644 classripple_1_1test_1_1LedgerRPC__test__inherit__graph.png create mode 100644 classripple_1_1test_1_1traffic__count__test-members.html create mode 100644 classripple_1_1test_1_1traffic__count__test.html create mode 100644 classripple_1_1test_1_1traffic__count__test__coll__graph.map create mode 100644 classripple_1_1test_1_1traffic__count__test__coll__graph.md5 create mode 100644 classripple_1_1test_1_1traffic__count__test__coll__graph.png create mode 100644 classripple_1_1test_1_1traffic__count__test__inherit__graph.map create mode 100644 classripple_1_1test_1_1traffic__count__test__inherit__graph.md5 create mode 100644 classripple_1_1test_1_1traffic__count__test__inherit__graph.png rename credentials_8cpp_source.html => creds_8cpp_source.html (98%) rename did_8cpp_source.html => dids_8cpp_source.html (98%) create mode 100644 inherit_graph_1013.map create mode 100644 inherit_graph_1013.md5 create mode 100644 inherit_graph_1013.png rename ledgerStateFix_8cpp_source.html => ledgerStateFixes_8cpp_source.html (98%) create mode 100644 structripple_1_1test_1_1traffic__count__test_1_1TestCase-members.html create mode 100644 structripple_1_1test_1_1traffic__count__test_1_1TestCase.html create mode 100644 structripple_1_1test_1_1traffic__count__test_1_1TestCase__coll__graph.map create mode 100644 structripple_1_1test_1_1traffic__count__test_1_1TestCase__coll__graph.md5 create mode 100644 structripple_1_1test_1_1traffic__count__test_1_1TestCase__coll__graph.png create mode 100644 traffic__count__test_8cpp_source.html diff --git a/AMMCalc__test_8cpp_source.html b/AMMCalc__test_8cpp_source.html index 2180c13bde..9a2c53f8b7 100644 --- a/AMMCalc__test_8cpp_source.html +++ b/AMMCalc__test_8cpp_source.html @@ -561,7 +561,7 @@ $(function() {
void run() override
Runs the suite.
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:120
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
Set the fee on a JTx.
Definition: fee.h:37
T endl(T... args)
diff --git a/AMMClawback__test_8cpp_source.html b/AMMClawback__test_8cpp_source.html index abc8591664..5216231d64 100644 --- a/AMMClawback__test_8cpp_source.html +++ b/AMMClawback__test_8cpp_source.html @@ -1904,7 +1904,7 @@ $(function() {
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:120
-
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:532
+
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:534
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
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
diff --git a/AMMExtended__test_8cpp_source.html b/AMMExtended__test_8cpp_source.html index 75f8b05b8a..08b18ba32e 100644 --- a/AMMExtended__test_8cpp_source.html +++ b/AMMExtended__test_8cpp_source.html @@ -4276,12 +4276,12 @@ $(function() {
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
A transaction testing environment.
Definition: Env.h:120
-
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:532
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:534
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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:258
-
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:767
+
Application & app()
Definition: Env.h:260
+
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:769
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
diff --git a/AMMTest_8cpp_source.html b/AMMTest_8cpp_source.html index f1d8e90528..0f8cd406fc 100644 --- a/AMMTest_8cpp_source.html +++ b/AMMTest_8cpp_source.html @@ -389,10 +389,10 @@ $(function() {
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:120
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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:258
+
Application & app()
Definition: Env.h:260
beast::Journal const journal
Definition: Env.h:161
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
Converts to IOU Issue or STAmount.
diff --git a/AMM_8cpp_source.html b/AMM_8cpp_source.html index 8c5fc4a6fd..8139678768 100644 --- a/AMM_8cpp_source.html +++ b/AMM_8cpp_source.html @@ -985,12 +985,12 @@ $(function() {
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:120
-
TER ter() const
Return the TER for the last JTx.
Definition: Env.h:583
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
TER ter() const
Return the TER for the last JTx.
Definition: Env.h:585
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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:258
+
Application & app()
Definition: Env.h:260
beast::Journal const journal
Definition: Env.h:161
-
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:767
+
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:769
Definition: AMM.h:40
A balance matches.
Definition: balance.h:39
Set the fee on a JTx.
Definition: fee.h:37
diff --git a/AMM__test_8cpp_source.html b/AMM__test_8cpp_source.html index 6268d12def..ed54046ea1 100644 --- a/AMM__test_8cpp_source.html +++ b/AMM__test_8cpp_source.html @@ -98,7176 +98,7181 @@ $(function() {
20#include <test/jtx.h>
21#include <test/jtx/AMM.h>
22#include <test/jtx/AMMTest.h>
-
23#include <test/jtx/amount.h>
-
24#include <test/jtx/sendmax.h>
-
25
-
26#include <xrpld/app/misc/AMMHelpers.h>
-
27#include <xrpld/app/misc/AMMUtils.h>
-
28#include <xrpld/app/tx/detail/AMMBid.h>
-
29
-
30#include <xrpl/basics/Number.h>
-
31#include <xrpl/protocol/AMMCore.h>
-
32#include <xrpl/protocol/Feature.h>
-
33
-
34#include <boost/regex.hpp>
-
35
-
36#include <utility>
-
37#include <vector>
-
38
-
39namespace ripple {
-
40namespace test {
-
41
-
46struct AMM_test : public jtx::AMMTest
-
47{
-
48private:
-
49 void
-
50 testInstanceCreate()
-
51 {
-
52 testcase("Instance Create");
-
53
-
54 using namespace jtx;
-
55
-
56 // XRP to IOU
-
57 testAMM([&](AMM& ammAlice, Env&) {
-
58 BEAST_EXPECT(ammAlice.expectBalances(
-
59 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
-
60 });
-
61
-
62 // IOU to IOU
-
63 testAMM(
-
64 [&](AMM& ammAlice, Env&) {
-
65 BEAST_EXPECT(ammAlice.expectBalances(
-
66 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
-
67 },
-
68 {{USD(20'000), BTC(0.5)}});
-
69
-
70 // IOU to IOU + transfer fee
-
71 {
-
72 Env env{*this};
-
73 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
-
74 env(rate(gw, 1.25));
-
75 env.close();
-
76 // no transfer fee on create
-
77 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
-
78 BEAST_EXPECT(ammAlice.expectBalances(
-
79 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
-
80 BEAST_EXPECT(expectLine(env, alice, USD(0)));
-
81 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
-
82 }
-
83
-
84 // Require authorization is set, account is authorized
-
85 {
-
86 Env env{*this};
-
87 env.fund(XRP(30'000), gw, alice);
-
88 env.close();
-
89 env(fset(gw, asfRequireAuth));
-
90 env(trust(alice, gw["USD"](30'000), 0));
-
91 env(trust(gw, alice["USD"](0), tfSetfAuth));
-
92 env.close();
-
93 env(pay(gw, alice, USD(10'000)));
-
94 env.close();
-
95 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
-
96 }
-
97
-
98 // Cleared global freeze
-
99 {
-
100 Env env{*this};
-
101 env.fund(XRP(30'000), gw, alice);
-
102 env.close();
-
103 env.trust(USD(30'000), alice);
-
104 env.close();
-
105 env(pay(gw, alice, USD(10'000)));
-
106 env.close();
-
107 env(fset(gw, asfGlobalFreeze));
-
108 env.close();
-
109 AMM ammAliceFail(
-
110 env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
-
111 env(fclear(gw, asfGlobalFreeze));
-
112 env.close();
-
113 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
-
114 }
-
115
-
116 // Trading fee
-
117 testAMM(
-
118 [&](AMM& amm, Env&) {
-
119 BEAST_EXPECT(amm.expectTradingFee(1'000));
-
120 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
-
121 },
-
122 std::nullopt,
-
123 1'000);
-
124
-
125 // Make sure asset comparison works.
-
126 BEAST_EXPECT(
-
127 STIssue(sfAsset, STAmount(XRP(2'000)).issue()) ==
-
128 STIssue(sfAsset, STAmount(XRP(2'000)).issue()));
-
129 BEAST_EXPECT(
-
130 STIssue(sfAsset, STAmount(XRP(2'000)).issue()) !=
-
131 STIssue(sfAsset, STAmount(USD(2'000)).issue()));
-
132 }
-
133
-
134 void
-
135 testInvalidInstance()
-
136 {
-
137 testcase("Invalid Instance");
-
138
-
139 using namespace jtx;
-
140
-
141 // Can't have both XRP tokens
-
142 {
-
143 Env env{*this};
-
144 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
145 AMM ammAlice(
-
146 env, alice, XRP(10'000), XRP(10'000), ter(temBAD_AMM_TOKENS));
-
147 BEAST_EXPECT(!ammAlice.ammExists());
-
148 }
-
149
-
150 // Can't have both tokens the same IOU
-
151 {
-
152 Env env{*this};
-
153 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
154 AMM ammAlice(
-
155 env, alice, USD(10'000), USD(10'000), ter(temBAD_AMM_TOKENS));
-
156 BEAST_EXPECT(!ammAlice.ammExists());
-
157 }
-
158
-
159 // Can't have zero or negative amounts
-
160 {
-
161 Env env{*this};
-
162 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
163 AMM ammAlice(env, alice, XRP(0), USD(10'000), ter(temBAD_AMOUNT));
-
164 BEAST_EXPECT(!ammAlice.ammExists());
-
165 AMM ammAlice1(env, alice, XRP(10'000), USD(0), ter(temBAD_AMOUNT));
-
166 BEAST_EXPECT(!ammAlice1.ammExists());
-
167 AMM ammAlice2(
-
168 env, alice, XRP(10'000), USD(-10'000), ter(temBAD_AMOUNT));
-
169 BEAST_EXPECT(!ammAlice2.ammExists());
-
170 AMM ammAlice3(
-
171 env, alice, XRP(-10'000), USD(10'000), ter(temBAD_AMOUNT));
-
172 BEAST_EXPECT(!ammAlice3.ammExists());
-
173 }
-
174
-
175 // Bad currency
-
176 {
-
177 Env env{*this};
-
178 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
179 AMM ammAlice(
-
180 env, alice, XRP(10'000), BAD(10'000), ter(temBAD_CURRENCY));
-
181 BEAST_EXPECT(!ammAlice.ammExists());
-
182 }
-
183
-
184 // Insufficient IOU balance
-
185 {
-
186 Env env{*this};
-
187 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
188 AMM ammAlice(
-
189 env, alice, XRP(10'000), USD(40'000), ter(tecUNFUNDED_AMM));
-
190 BEAST_EXPECT(!ammAlice.ammExists());
-
191 }
-
192
-
193 // Insufficient XRP balance
-
194 {
-
195 Env env{*this};
-
196 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
197 AMM ammAlice(
-
198 env, alice, XRP(40'000), USD(10'000), ter(tecUNFUNDED_AMM));
-
199 BEAST_EXPECT(!ammAlice.ammExists());
-
200 }
-
201
-
202 // Invalid trading fee
-
203 {
-
204 Env env{*this};
-
205 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
206 AMM ammAlice(
-
207 env,
-
208 alice,
-
209 XRP(10'000),
-
210 USD(10'000),
-
211 false,
-
212 65'001,
-
213 10,
-
214 std::nullopt,
+
23#include <test/jtx/CaptureLogs.h>
+
24#include <test/jtx/amount.h>
+
25#include <test/jtx/sendmax.h>
+
26
+
27#include <xrpld/app/misc/AMMHelpers.h>
+
28#include <xrpld/app/misc/AMMUtils.h>
+
29#include <xrpld/app/tx/detail/AMMBid.h>
+
30
+
31#include <xrpl/basics/Number.h>
+
32#include <xrpl/protocol/AMMCore.h>
+
33#include <xrpl/protocol/Feature.h>
+
34
+
35#include <boost/regex.hpp>
+
36
+
37#include <utility>
+
38#include <vector>
+
39
+
40namespace ripple {
+
41namespace test {
+
42
+
47struct AMM_test : public jtx::AMMTest
+
48{
+
49private:
+
50 void
+
51 testInstanceCreate()
+
52 {
+
53 testcase("Instance Create");
+
54
+
55 using namespace jtx;
+
56
+
57 // XRP to IOU
+
58 testAMM([&](AMM& ammAlice, Env&) {
+
59 BEAST_EXPECT(ammAlice.expectBalances(
+
60 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
+
61 });
+
62
+
63 // IOU to IOU
+
64 testAMM(
+
65 [&](AMM& ammAlice, Env&) {
+
66 BEAST_EXPECT(ammAlice.expectBalances(
+
67 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
+
68 },
+
69 {{USD(20'000), BTC(0.5)}});
+
70
+
71 // IOU to IOU + transfer fee
+
72 {
+
73 Env env{*this};
+
74 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
+
75 env(rate(gw, 1.25));
+
76 env.close();
+
77 // no transfer fee on create
+
78 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
+
79 BEAST_EXPECT(ammAlice.expectBalances(
+
80 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
+
81 BEAST_EXPECT(expectLine(env, alice, USD(0)));
+
82 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
+
83 }
+
84
+
85 // Require authorization is set, account is authorized
+
86 {
+
87 Env env{*this};
+
88 env.fund(XRP(30'000), gw, alice);
+
89 env.close();
+
90 env(fset(gw, asfRequireAuth));
+
91 env(trust(alice, gw["USD"](30'000), 0));
+
92 env(trust(gw, alice["USD"](0), tfSetfAuth));
+
93 env.close();
+
94 env(pay(gw, alice, USD(10'000)));
+
95 env.close();
+
96 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
+
97 }
+
98
+
99 // Cleared global freeze
+
100 {
+
101 Env env{*this};
+
102 env.fund(XRP(30'000), gw, alice);
+
103 env.close();
+
104 env.trust(USD(30'000), alice);
+
105 env.close();
+
106 env(pay(gw, alice, USD(10'000)));
+
107 env.close();
+
108 env(fset(gw, asfGlobalFreeze));
+
109 env.close();
+
110 AMM ammAliceFail(
+
111 env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
+
112 env(fclear(gw, asfGlobalFreeze));
+
113 env.close();
+
114 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
+
115 }
+
116
+
117 // Trading fee
+
118 testAMM(
+
119 [&](AMM& amm, Env&) {
+
120 BEAST_EXPECT(amm.expectTradingFee(1'000));
+
121 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
+
122 },
+
123 std::nullopt,
+
124 1'000);
+
125
+
126 // Make sure asset comparison works.
+
127 BEAST_EXPECT(
+
128 STIssue(sfAsset, STAmount(XRP(2'000)).issue()) ==
+
129 STIssue(sfAsset, STAmount(XRP(2'000)).issue()));
+
130 BEAST_EXPECT(
+
131 STIssue(sfAsset, STAmount(XRP(2'000)).issue()) !=
+
132 STIssue(sfAsset, STAmount(USD(2'000)).issue()));
+
133 }
+
134
+
135 void
+
136 testInvalidInstance()
+
137 {
+
138 testcase("Invalid Instance");
+
139
+
140 using namespace jtx;
+
141
+
142 // Can't have both XRP tokens
+
143 {
+
144 Env env{*this};
+
145 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
146 AMM ammAlice(
+
147 env, alice, XRP(10'000), XRP(10'000), ter(temBAD_AMM_TOKENS));
+
148 BEAST_EXPECT(!ammAlice.ammExists());
+
149 }
+
150
+
151 // Can't have both tokens the same IOU
+
152 {
+
153 Env env{*this};
+
154 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
155 AMM ammAlice(
+
156 env, alice, USD(10'000), USD(10'000), ter(temBAD_AMM_TOKENS));
+
157 BEAST_EXPECT(!ammAlice.ammExists());
+
158 }
+
159
+
160 // Can't have zero or negative amounts
+
161 {
+
162 Env env{*this};
+
163 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
164 AMM ammAlice(env, alice, XRP(0), USD(10'000), ter(temBAD_AMOUNT));
+
165 BEAST_EXPECT(!ammAlice.ammExists());
+
166 AMM ammAlice1(env, alice, XRP(10'000), USD(0), ter(temBAD_AMOUNT));
+
167 BEAST_EXPECT(!ammAlice1.ammExists());
+
168 AMM ammAlice2(
+
169 env, alice, XRP(10'000), USD(-10'000), ter(temBAD_AMOUNT));
+
170 BEAST_EXPECT(!ammAlice2.ammExists());
+
171 AMM ammAlice3(
+
172 env, alice, XRP(-10'000), USD(10'000), ter(temBAD_AMOUNT));
+
173 BEAST_EXPECT(!ammAlice3.ammExists());
+
174 }
+
175
+
176 // Bad currency
+
177 {
+
178 Env env{*this};
+
179 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
180 AMM ammAlice(
+
181 env, alice, XRP(10'000), BAD(10'000), ter(temBAD_CURRENCY));
+
182 BEAST_EXPECT(!ammAlice.ammExists());
+
183 }
+
184
+
185 // Insufficient IOU balance
+
186 {
+
187 Env env{*this};
+
188 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
189 AMM ammAlice(
+
190 env, alice, XRP(10'000), USD(40'000), ter(tecUNFUNDED_AMM));
+
191 BEAST_EXPECT(!ammAlice.ammExists());
+
192 }
+
193
+
194 // Insufficient XRP balance
+
195 {
+
196 Env env{*this};
+
197 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
198 AMM ammAlice(
+
199 env, alice, XRP(40'000), USD(10'000), ter(tecUNFUNDED_AMM));
+
200 BEAST_EXPECT(!ammAlice.ammExists());
+
201 }
+
202
+
203 // Invalid trading fee
+
204 {
+
205 Env env{*this};
+
206 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
207 AMM ammAlice(
+
208 env,
+
209 alice,
+
210 XRP(10'000),
+
211 USD(10'000),
+
212 false,
+
213 65'001,
+
214 10,
215 std::nullopt,
216 std::nullopt,
-
217 ter(temBAD_FEE));
-
218 BEAST_EXPECT(!ammAlice.ammExists());
-
219 }
-
220
-
221 // AMM already exists
-
222 testAMM([&](AMM& ammAlice, Env& env) {
-
223 AMM ammCarol(
-
224 env, carol, XRP(10'000), USD(10'000), ter(tecDUPLICATE));
-
225 });
-
226
-
227 // Invalid flags
-
228 {
-
229 Env env{*this};
-
230 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
-
231 AMM ammAlice(
-
232 env,
-
233 alice,
-
234 XRP(10'000),
-
235 USD(10'000),
-
236 false,
-
237 0,
-
238 10,
-
239 tfWithdrawAll,
-
240 std::nullopt,
+
217 std::nullopt,
+
218 ter(temBAD_FEE));
+
219 BEAST_EXPECT(!ammAlice.ammExists());
+
220 }
+
221
+
222 // AMM already exists
+
223 testAMM([&](AMM& ammAlice, Env& env) {
+
224 AMM ammCarol(
+
225 env, carol, XRP(10'000), USD(10'000), ter(tecDUPLICATE));
+
226 });
+
227
+
228 // Invalid flags
+
229 {
+
230 Env env{*this};
+
231 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
+
232 AMM ammAlice(
+
233 env,
+
234 alice,
+
235 XRP(10'000),
+
236 USD(10'000),
+
237 false,
+
238 0,
+
239 10,
+
240 tfWithdrawAll,
241 std::nullopt,
-
242 ter(temINVALID_FLAG));
-
243 BEAST_EXPECT(!ammAlice.ammExists());
-
244 }
-
245
-
246 // Invalid Account
-
247 {
-
248 Env env{*this};
-
249 Account bad("bad");
-
250 env.memoize(bad);
-
251 AMM ammAlice(
-
252 env,
-
253 bad,
-
254 XRP(10'000),
-
255 USD(10'000),
-
256 false,
-
257 0,
-
258 10,
-
259 std::nullopt,
-
260 seq(1),
-
261 std::nullopt,
-
262 ter(terNO_ACCOUNT));
-
263 BEAST_EXPECT(!ammAlice.ammExists());
-
264 }
-
265
-
266 // Require authorization is set
-
267 {
-
268 Env env{*this};
-
269 env.fund(XRP(30'000), gw, alice);
-
270 env.close();
-
271 env(fset(gw, asfRequireAuth));
-
272 env.close();
-
273 env(trust(gw, alice["USD"](30'000)));
-
274 env.close();
-
275 AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecNO_AUTH));
-
276 BEAST_EXPECT(!ammAlice.ammExists());
-
277 }
-
278
-
279 // Globally frozen
-
280 {
-
281 Env env{*this};
-
282 env.fund(XRP(30'000), gw, alice);
-
283 env.close();
-
284 env(fset(gw, asfGlobalFreeze));
-
285 env.close();
-
286 env(trust(gw, alice["USD"](30'000)));
-
287 env.close();
-
288 AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
-
289 BEAST_EXPECT(!ammAlice.ammExists());
-
290 }
-
291
-
292 // Individually frozen
-
293 {
-
294 Env env{*this};
-
295 env.fund(XRP(30'000), gw, alice);
-
296 env.close();
-
297 env(trust(gw, alice["USD"](30'000)));
-
298 env.close();
-
299 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
300 env.close();
-
301 AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
-
302 BEAST_EXPECT(!ammAlice.ammExists());
-
303 }
-
304
-
305 // Insufficient reserve, XRP/IOU
-
306 {
-
307 Env env(*this);
-
308 auto const starting_xrp =
-
309 XRP(1'000) + reserve(env, 3) + env.current()->fees().base * 4;
-
310 env.fund(starting_xrp, gw);
-
311 env.fund(starting_xrp, alice);
-
312 env.trust(USD(2'000), alice);
-
313 env.close();
-
314 env(pay(gw, alice, USD(2'000)));
-
315 env.close();
-
316 env(offer(alice, XRP(101), USD(100)));
-
317 env(offer(alice, XRP(102), USD(100)));
-
318 AMM ammAlice(
-
319 env, alice, XRP(1'000), USD(1'000), ter(tecUNFUNDED_AMM));
-
320 }
-
321
-
322 // Insufficient reserve, IOU/IOU
-
323 {
-
324 Env env(*this);
-
325 auto const starting_xrp =
-
326 reserve(env, 4) + env.current()->fees().base * 5;
-
327 env.fund(starting_xrp, gw);
-
328 env.fund(starting_xrp, alice);
-
329 env.trust(USD(2'000), alice);
-
330 env.trust(EUR(2'000), alice);
-
331 env.close();
-
332 env(pay(gw, alice, USD(2'000)));
-
333 env(pay(gw, alice, EUR(2'000)));
-
334 env.close();
-
335 env(offer(alice, EUR(101), USD(100)));
-
336 env(offer(alice, EUR(102), USD(100)));
-
337 AMM ammAlice(
-
338 env, alice, EUR(1'000), USD(1'000), ter(tecINSUF_RESERVE_LINE));
-
339 }
-
340
-
341 // Insufficient fee
-
342 {
-
343 Env env(*this);
-
344 fund(env, gw, {alice}, XRP(2'000), {USD(2'000), EUR(2'000)});
-
345 AMM ammAlice(
-
346 env,
-
347 alice,
-
348 EUR(1'000),
-
349 USD(1'000),
-
350 false,
-
351 0,
-
352 ammCrtFee(env).drops() - 1,
-
353 std::nullopt,
+
242 std::nullopt,
+
243 ter(temINVALID_FLAG));
+
244 BEAST_EXPECT(!ammAlice.ammExists());
+
245 }
+
246
+
247 // Invalid Account
+
248 {
+
249 Env env{*this};
+
250 Account bad("bad");
+
251 env.memoize(bad);
+
252 AMM ammAlice(
+
253 env,
+
254 bad,
+
255 XRP(10'000),
+
256 USD(10'000),
+
257 false,
+
258 0,
+
259 10,
+
260 std::nullopt,
+
261 seq(1),
+
262 std::nullopt,
+
263 ter(terNO_ACCOUNT));
+
264 BEAST_EXPECT(!ammAlice.ammExists());
+
265 }
+
266
+
267 // Require authorization is set
+
268 {
+
269 Env env{*this};
+
270 env.fund(XRP(30'000), gw, alice);
+
271 env.close();
+
272 env(fset(gw, asfRequireAuth));
+
273 env.close();
+
274 env(trust(gw, alice["USD"](30'000)));
+
275 env.close();
+
276 AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecNO_AUTH));
+
277 BEAST_EXPECT(!ammAlice.ammExists());
+
278 }
+
279
+
280 // Globally frozen
+
281 {
+
282 Env env{*this};
+
283 env.fund(XRP(30'000), gw, alice);
+
284 env.close();
+
285 env(fset(gw, asfGlobalFreeze));
+
286 env.close();
+
287 env(trust(gw, alice["USD"](30'000)));
+
288 env.close();
+
289 AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
+
290 BEAST_EXPECT(!ammAlice.ammExists());
+
291 }
+
292
+
293 // Individually frozen
+
294 {
+
295 Env env{*this};
+
296 env.fund(XRP(30'000), gw, alice);
+
297 env.close();
+
298 env(trust(gw, alice["USD"](30'000)));
+
299 env.close();
+
300 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
301 env.close();
+
302 AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
+
303 BEAST_EXPECT(!ammAlice.ammExists());
+
304 }
+
305
+
306 // Insufficient reserve, XRP/IOU
+
307 {
+
308 Env env(*this);
+
309 auto const starting_xrp =
+
310 XRP(1'000) + reserve(env, 3) + env.current()->fees().base * 4;
+
311 env.fund(starting_xrp, gw);
+
312 env.fund(starting_xrp, alice);
+
313 env.trust(USD(2'000), alice);
+
314 env.close();
+
315 env(pay(gw, alice, USD(2'000)));
+
316 env.close();
+
317 env(offer(alice, XRP(101), USD(100)));
+
318 env(offer(alice, XRP(102), USD(100)));
+
319 AMM ammAlice(
+
320 env, alice, XRP(1'000), USD(1'000), ter(tecUNFUNDED_AMM));
+
321 }
+
322
+
323 // Insufficient reserve, IOU/IOU
+
324 {
+
325 Env env(*this);
+
326 auto const starting_xrp =
+
327 reserve(env, 4) + env.current()->fees().base * 5;
+
328 env.fund(starting_xrp, gw);
+
329 env.fund(starting_xrp, alice);
+
330 env.trust(USD(2'000), alice);
+
331 env.trust(EUR(2'000), alice);
+
332 env.close();
+
333 env(pay(gw, alice, USD(2'000)));
+
334 env(pay(gw, alice, EUR(2'000)));
+
335 env.close();
+
336 env(offer(alice, EUR(101), USD(100)));
+
337 env(offer(alice, EUR(102), USD(100)));
+
338 AMM ammAlice(
+
339 env, alice, EUR(1'000), USD(1'000), ter(tecINSUF_RESERVE_LINE));
+
340 }
+
341
+
342 // Insufficient fee
+
343 {
+
344 Env env(*this);
+
345 fund(env, gw, {alice}, XRP(2'000), {USD(2'000), EUR(2'000)});
+
346 AMM ammAlice(
+
347 env,
+
348 alice,
+
349 EUR(1'000),
+
350 USD(1'000),
+
351 false,
+
352 0,
+
353 ammCrtFee(env).drops() - 1,
354 std::nullopt,
355 std::nullopt,
-
356 ter(telINSUF_FEE_P));
-
357 }
-
358
-
359 // AMM with LPTokens
-
360
-
361 // AMM with one LPToken from another AMM.
-
362 testAMM([&](AMM& ammAlice, Env& env) {
-
363 fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly);
-
364 AMM ammAMMToken(
-
365 env,
-
366 alice,
-
367 EUR(10'000),
-
368 STAmount{ammAlice.lptIssue(), 1'000'000},
-
369 ter(tecAMM_INVALID_TOKENS));
-
370 AMM ammAMMToken1(
-
371 env,
-
372 alice,
-
373 STAmount{ammAlice.lptIssue(), 1'000'000},
-
374 EUR(10'000),
-
375 ter(tecAMM_INVALID_TOKENS));
-
376 });
-
377
-
378 // AMM with two LPTokens from other AMMs.
-
379 testAMM([&](AMM& ammAlice, Env& env) {
-
380 fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly);
-
381 AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000));
-
382 auto const token1 = ammAlice.lptIssue();
-
383 auto const token2 = ammAlice1.lptIssue();
-
384 AMM ammAMMTokens(
-
385 env,
-
386 alice,
-
387 STAmount{token1, 1'000'000},
-
388 STAmount{token2, 1'000'000},
-
389 ter(tecAMM_INVALID_TOKENS));
-
390 });
-
391
-
392 // Issuer has DefaultRipple disabled
-
393 {
-
394 Env env(*this);
-
395 env.fund(XRP(30'000), gw);
-
396 env(fclear(gw, asfDefaultRipple));
-
397 AMM ammGw(env, gw, XRP(10'000), USD(10'000), ter(terNO_RIPPLE));
-
398 env.fund(XRP(30'000), alice);
-
399 env.trust(USD(30'000), alice);
-
400 env(pay(gw, alice, USD(30'000)));
-
401 AMM ammAlice(
-
402 env, alice, XRP(10'000), USD(10'000), ter(terNO_RIPPLE));
-
403 Account const gw1("gw1");
-
404 env.fund(XRP(30'000), gw1);
-
405 env(fclear(gw1, asfDefaultRipple));
-
406 env.trust(USD(30'000), gw1);
-
407 env(pay(gw, gw1, USD(30'000)));
-
408 auto const USD1 = gw1["USD"];
-
409 AMM ammGwGw1(env, gw, USD(10'000), USD1(10'000), ter(terNO_RIPPLE));
-
410 env.trust(USD1(30'000), alice);
-
411 env(pay(gw1, alice, USD1(30'000)));
-
412 AMM ammAlice1(
-
413 env, alice, USD(10'000), USD1(10'000), ter(terNO_RIPPLE));
-
414 }
-
415 }
-
416
-
417 void
-
418 testInvalidDeposit(FeatureBitset features)
-
419 {
-
420 testcase("Invalid Deposit");
-
421
-
422 using namespace jtx;
-
423
-
424 testAMM([&](AMM& ammAlice, Env& env) {
-
425 // Invalid flags
-
426 ammAlice.deposit(
-
427 alice,
-
428 1'000'000,
-
429 std::nullopt,
-
430 tfWithdrawAll,
-
431 ter(temINVALID_FLAG));
-
432
-
433 // Invalid options
-
434 std::vector<std::tuple<
-
435 std::optional<std::uint32_t>,
+
356 std::nullopt,
+
357 ter(telINSUF_FEE_P));
+
358 }
+
359
+
360 // AMM with LPTokens
+
361
+
362 // AMM with one LPToken from another AMM.
+
363 testAMM([&](AMM& ammAlice, Env& env) {
+
364 fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly);
+
365 AMM ammAMMToken(
+
366 env,
+
367 alice,
+
368 EUR(10'000),
+
369 STAmount{ammAlice.lptIssue(), 1'000'000},
+
370 ter(tecAMM_INVALID_TOKENS));
+
371 AMM ammAMMToken1(
+
372 env,
+
373 alice,
+
374 STAmount{ammAlice.lptIssue(), 1'000'000},
+
375 EUR(10'000),
+
376 ter(tecAMM_INVALID_TOKENS));
+
377 });
+
378
+
379 // AMM with two LPTokens from other AMMs.
+
380 testAMM([&](AMM& ammAlice, Env& env) {
+
381 fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly);
+
382 AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000));
+
383 auto const token1 = ammAlice.lptIssue();
+
384 auto const token2 = ammAlice1.lptIssue();
+
385 AMM ammAMMTokens(
+
386 env,
+
387 alice,
+
388 STAmount{token1, 1'000'000},
+
389 STAmount{token2, 1'000'000},
+
390 ter(tecAMM_INVALID_TOKENS));
+
391 });
+
392
+
393 // Issuer has DefaultRipple disabled
+
394 {
+
395 Env env(*this);
+
396 env.fund(XRP(30'000), gw);
+
397 env(fclear(gw, asfDefaultRipple));
+
398 AMM ammGw(env, gw, XRP(10'000), USD(10'000), ter(terNO_RIPPLE));
+
399 env.fund(XRP(30'000), alice);
+
400 env.trust(USD(30'000), alice);
+
401 env(pay(gw, alice, USD(30'000)));
+
402 AMM ammAlice(
+
403 env, alice, XRP(10'000), USD(10'000), ter(terNO_RIPPLE));
+
404 Account const gw1("gw1");
+
405 env.fund(XRP(30'000), gw1);
+
406 env(fclear(gw1, asfDefaultRipple));
+
407 env.trust(USD(30'000), gw1);
+
408 env(pay(gw, gw1, USD(30'000)));
+
409 auto const USD1 = gw1["USD"];
+
410 AMM ammGwGw1(env, gw, USD(10'000), USD1(10'000), ter(terNO_RIPPLE));
+
411 env.trust(USD1(30'000), alice);
+
412 env(pay(gw1, alice, USD1(30'000)));
+
413 AMM ammAlice1(
+
414 env, alice, USD(10'000), USD1(10'000), ter(terNO_RIPPLE));
+
415 }
+
416 }
+
417
+
418 void
+
419 testInvalidDeposit(FeatureBitset features)
+
420 {
+
421 testcase("Invalid Deposit");
+
422
+
423 using namespace jtx;
+
424
+
425 testAMM([&](AMM& ammAlice, Env& env) {
+
426 // Invalid flags
+
427 ammAlice.deposit(
+
428 alice,
+
429 1'000'000,
+
430 std::nullopt,
+
431 tfWithdrawAll,
+
432 ter(temINVALID_FLAG));
+
433
+
434 // Invalid options
+
435 std::vector<std::tuple<
436 std::optional<std::uint32_t>,
-
437 std::optional<STAmount>,
+
437 std::optional<std::uint32_t>,
438 std::optional<STAmount>,
439 std::optional<STAmount>,
-
440 std::optional<std::uint16_t>>>
-
441 invalidOptions = {
-
442 // flags, tokens, asset1In, asset2in, EPrice, tfee
-
443 {tfLPToken,
-
444 1'000,
-
445 std::nullopt,
-
446 USD(100),
-
447 std::nullopt,
-
448 std::nullopt},
-
449 {tfLPToken,
-
450 1'000,
-
451 XRP(100),
-
452 std::nullopt,
+
440 std::optional<STAmount>,
+
441 std::optional<std::uint16_t>>>
+
442 invalidOptions = {
+
443 // flags, tokens, asset1In, asset2in, EPrice, tfee
+
444 {tfLPToken,
+
445 1'000,
+
446 std::nullopt,
+
447 USD(100),
+
448 std::nullopt,
+
449 std::nullopt},
+
450 {tfLPToken,
+
451 1'000,
+
452 XRP(100),
453 std::nullopt,
-
454 std::nullopt},
-
455 {tfLPToken,
-
456 1'000,
-
457 std::nullopt,
+
454 std::nullopt,
+
455 std::nullopt},
+
456 {tfLPToken,
+
457 1'000,
458 std::nullopt,
-
459 STAmount{USD, 1, -1},
-
460 std::nullopt},
-
461 {tfLPToken,
-
462 std::nullopt,
-
463 USD(100),
-
464 std::nullopt,
-
465 STAmount{USD, 1, -1},
-
466 std::nullopt},
-
467 {tfLPToken,
-
468 1'000,
-
469 XRP(100),
-
470 std::nullopt,
-
471 STAmount{USD, 1, -1},
-
472 std::nullopt},
-
473 {tfLPToken,
-
474 1'000,
-
475 std::nullopt,
+
459 std::nullopt,
+
460 STAmount{USD, 1, -1},
+
461 std::nullopt},
+
462 {tfLPToken,
+
463 std::nullopt,
+
464 USD(100),
+
465 std::nullopt,
+
466 STAmount{USD, 1, -1},
+
467 std::nullopt},
+
468 {tfLPToken,
+
469 1'000,
+
470 XRP(100),
+
471 std::nullopt,
+
472 STAmount{USD, 1, -1},
+
473 std::nullopt},
+
474 {tfLPToken,
+
475 1'000,
476 std::nullopt,
477 std::nullopt,
-
478 1'000},
-
479 {tfSingleAsset,
-
480 1'000,
-
481 std::nullopt,
+
478 std::nullopt,
+
479 1'000},
+
480 {tfSingleAsset,
+
481 1'000,
482 std::nullopt,
483 std::nullopt,
-
484 std::nullopt},
-
485 {tfSingleAsset,
-
486 std::nullopt,
+
484 std::nullopt,
+
485 std::nullopt},
+
486 {tfSingleAsset,
487 std::nullopt,
-
488 USD(100),
-
489 std::nullopt,
-
490 std::nullopt},
-
491 {tfSingleAsset,
-
492 std::nullopt,
+
488 std::nullopt,
+
489 USD(100),
+
490 std::nullopt,
+
491 std::nullopt},
+
492 {tfSingleAsset,
493 std::nullopt,
494 std::nullopt,
-
495 STAmount{USD, 1, -1},
-
496 std::nullopt},
-
497 {tfSingleAsset,
-
498 std::nullopt,
-
499 USD(100),
-
500 std::nullopt,
+
495 std::nullopt,
+
496 STAmount{USD, 1, -1},
+
497 std::nullopt},
+
498 {tfSingleAsset,
+
499 std::nullopt,
+
500 USD(100),
501 std::nullopt,
-
502 1'000},
-
503 {tfTwoAsset,
-
504 1'000,
-
505 std::nullopt,
+
502 std::nullopt,
+
503 1'000},
+
504 {tfTwoAsset,
+
505 1'000,
506 std::nullopt,
507 std::nullopt,
-
508 std::nullopt},
-
509 {tfTwoAsset,
-
510 std::nullopt,
-
511 XRP(100),
-
512 USD(100),
-
513 STAmount{USD, 1, -1},
-
514 std::nullopt},
-
515 {tfTwoAsset,
-
516 std::nullopt,
-
517 XRP(100),
-
518 std::nullopt,
+
508 std::nullopt,
+
509 std::nullopt},
+
510 {tfTwoAsset,
+
511 std::nullopt,
+
512 XRP(100),
+
513 USD(100),
+
514 STAmount{USD, 1, -1},
+
515 std::nullopt},
+
516 {tfTwoAsset,
+
517 std::nullopt,
+
518 XRP(100),
519 std::nullopt,
-
520 std::nullopt},
-
521 {tfTwoAsset,
-
522 std::nullopt,
-
523 XRP(100),
-
524 USD(100),
-
525 std::nullopt,
-
526 1'000},
-
527 {tfTwoAsset,
-
528 std::nullopt,
+
520 std::nullopt,
+
521 std::nullopt},
+
522 {tfTwoAsset,
+
523 std::nullopt,
+
524 XRP(100),
+
525 USD(100),
+
526 std::nullopt,
+
527 1'000},
+
528 {tfTwoAsset,
529 std::nullopt,
-
530 USD(100),
-
531 STAmount{USD, 1, -1},
-
532 std::nullopt},
-
533 {tfOneAssetLPToken,
-
534 1'000,
-
535 std::nullopt,
+
530 std::nullopt,
+
531 USD(100),
+
532 STAmount{USD, 1, -1},
+
533 std::nullopt},
+
534 {tfOneAssetLPToken,
+
535 1'000,
536 std::nullopt,
537 std::nullopt,
-
538 std::nullopt},
-
539 {tfOneAssetLPToken,
-
540 std::nullopt,
-
541 XRP(100),
-
542 USD(100),
-
543 std::nullopt,
-
544 std::nullopt},
-
545 {tfOneAssetLPToken,
-
546 std::nullopt,
-
547 XRP(100),
-
548 std::nullopt,
-
549 STAmount{USD, 1, -1},
-
550 std::nullopt},
-
551 {tfOneAssetLPToken,
-
552 1'000,
-
553 XRP(100),
-
554 std::nullopt,
+
538 std::nullopt,
+
539 std::nullopt},
+
540 {tfOneAssetLPToken,
+
541 std::nullopt,
+
542 XRP(100),
+
543 USD(100),
+
544 std::nullopt,
+
545 std::nullopt},
+
546 {tfOneAssetLPToken,
+
547 std::nullopt,
+
548 XRP(100),
+
549 std::nullopt,
+
550 STAmount{USD, 1, -1},
+
551 std::nullopt},
+
552 {tfOneAssetLPToken,
+
553 1'000,
+
554 XRP(100),
555 std::nullopt,
-
556 1'000},
-
557 {tfLimitLPToken,
-
558 1'000,
-
559 std::nullopt,
+
556 std::nullopt,
+
557 1'000},
+
558 {tfLimitLPToken,
+
559 1'000,
560 std::nullopt,
561 std::nullopt,
-
562 std::nullopt},
-
563 {tfLimitLPToken,
-
564 1'000,
-
565 USD(100),
-
566 std::nullopt,
+
562 std::nullopt,
+
563 std::nullopt},
+
564 {tfLimitLPToken,
+
565 1'000,
+
566 USD(100),
567 std::nullopt,
-
568 std::nullopt},
-
569 {tfLimitLPToken,
-
570 std::nullopt,
-
571 USD(100),
-
572 XRP(100),
-
573 std::nullopt,
-
574 std::nullopt},
-
575 {tfLimitLPToken,
-
576 std::nullopt,
-
577 XRP(100),
-
578 std::nullopt,
-
579 STAmount{USD, 1, -1},
-
580 1'000},
-
581 {tfTwoAssetIfEmpty,
-
582 std::nullopt,
+
568 std::nullopt,
+
569 std::nullopt},
+
570 {tfLimitLPToken,
+
571 std::nullopt,
+
572 USD(100),
+
573 XRP(100),
+
574 std::nullopt,
+
575 std::nullopt},
+
576 {tfLimitLPToken,
+
577 std::nullopt,
+
578 XRP(100),
+
579 std::nullopt,
+
580 STAmount{USD, 1, -1},
+
581 1'000},
+
582 {tfTwoAssetIfEmpty,
583 std::nullopt,
584 std::nullopt,
585 std::nullopt,
-
586 1'000},
-
587 {tfTwoAssetIfEmpty,
-
588 1'000,
-
589 std::nullopt,
+
586 std::nullopt,
+
587 1'000},
+
588 {tfTwoAssetIfEmpty,
+
589 1'000,
590 std::nullopt,
591 std::nullopt,
-
592 std::nullopt},
-
593 {tfTwoAssetIfEmpty,
-
594 std::nullopt,
-
595 XRP(100),
-
596 USD(100),
-
597 STAmount{USD, 1, -1},
-
598 std::nullopt},
-
599 {tfTwoAssetIfEmpty | tfLPToken,
-
600 std::nullopt,
-
601 XRP(100),
-
602 USD(100),
-
603 STAmount{USD, 1, -1},
-
604 std::nullopt}};
-
605 for (auto const& it : invalidOptions)
-
606 {
-
607 ammAlice.deposit(
-
608 alice,
-
609 std::get<1>(it),
-
610 std::get<2>(it),
-
611 std::get<3>(it),
-
612 std::get<4>(it),
-
613 std::get<0>(it),
-
614 std::nullopt,
+
592 std::nullopt,
+
593 std::nullopt},
+
594 {tfTwoAssetIfEmpty,
+
595 std::nullopt,
+
596 XRP(100),
+
597 USD(100),
+
598 STAmount{USD, 1, -1},
+
599 std::nullopt},
+
600 {tfTwoAssetIfEmpty | tfLPToken,
+
601 std::nullopt,
+
602 XRP(100),
+
603 USD(100),
+
604 STAmount{USD, 1, -1},
+
605 std::nullopt}};
+
606 for (auto const& it : invalidOptions)
+
607 {
+
608 ammAlice.deposit(
+
609 alice,
+
610 std::get<1>(it),
+
611 std::get<2>(it),
+
612 std::get<3>(it),
+
613 std::get<4>(it),
+
614 std::get<0>(it),
615 std::nullopt,
-
616 std::get<5>(it),
-
617 ter(temMALFORMED));
-
618 }
-
619
-
620 {
-
621 // bad preflight1
-
622 Json::Value jv = Json::objectValue;
-
623 jv[jss::Account] = alice.human();
-
624 jv[jss::TransactionType] = jss::AMMDeposit;
-
625 jv[jss::Asset] =
-
626 STIssue(sfAsset, XRP).getJson(JsonOptions::none);
-
627 jv[jss::Asset2] =
-
628 STIssue(sfAsset, USD).getJson(JsonOptions::none);
-
629 jv[jss::Fee] = "-1";
-
630 env(jv, ter(temBAD_FEE));
-
631 }
-
632
-
633 // Invalid tokens
-
634 ammAlice.deposit(
-
635 alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
-
636 ammAlice.deposit(
-
637 alice,
-
638 IOUAmount{-1},
-
639 std::nullopt,
+
616 std::nullopt,
+
617 std::get<5>(it),
+
618 ter(temMALFORMED));
+
619 }
+
620
+
621 {
+
622 // bad preflight1
+
623 Json::Value jv = Json::objectValue;
+
624 jv[jss::Account] = alice.human();
+
625 jv[jss::TransactionType] = jss::AMMDeposit;
+
626 jv[jss::Asset] =
+
627 STIssue(sfAsset, XRP).getJson(JsonOptions::none);
+
628 jv[jss::Asset2] =
+
629 STIssue(sfAsset, USD).getJson(JsonOptions::none);
+
630 jv[jss::Fee] = "-1";
+
631 env(jv, ter(temBAD_FEE));
+
632 }
+
633
+
634 // Invalid tokens
+
635 ammAlice.deposit(
+
636 alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
+
637 ammAlice.deposit(
+
638 alice,
+
639 IOUAmount{-1},
640 std::nullopt,
-
641 ter(temBAD_AMM_TOKENS));
-
642
-
643 {
-
644 Json::Value jv = Json::objectValue;
-
645 jv[jss::Account] = alice.human();
-
646 jv[jss::TransactionType] = jss::AMMDeposit;
-
647 jv[jss::Asset] =
-
648 STIssue(sfAsset, XRP).getJson(JsonOptions::none);
-
649 jv[jss::Asset2] =
-
650 STIssue(sfAsset, USD).getJson(JsonOptions::none);
-
651 jv[jss::LPTokenOut] =
-
652 USD(100).value().getJson(JsonOptions::none);
-
653 jv[jss::Flags] = tfLPToken;
-
654 env(jv, ter(temBAD_AMM_TOKENS));
-
655 }
-
656
-
657 // Invalid trading fee
-
658 ammAlice.deposit(
-
659 carol,
-
660 std::nullopt,
-
661 XRP(200),
-
662 USD(200),
-
663 std::nullopt,
-
664 tfTwoAssetIfEmpty,
-
665 std::nullopt,
+
641 std::nullopt,
+
642 ter(temBAD_AMM_TOKENS));
+
643
+
644 {
+
645 Json::Value jv = Json::objectValue;
+
646 jv[jss::Account] = alice.human();
+
647 jv[jss::TransactionType] = jss::AMMDeposit;
+
648 jv[jss::Asset] =
+
649 STIssue(sfAsset, XRP).getJson(JsonOptions::none);
+
650 jv[jss::Asset2] =
+
651 STIssue(sfAsset, USD).getJson(JsonOptions::none);
+
652 jv[jss::LPTokenOut] =
+
653 USD(100).value().getJson(JsonOptions::none);
+
654 jv[jss::Flags] = tfLPToken;
+
655 env(jv, ter(temBAD_AMM_TOKENS));
+
656 }
+
657
+
658 // Invalid trading fee
+
659 ammAlice.deposit(
+
660 carol,
+
661 std::nullopt,
+
662 XRP(200),
+
663 USD(200),
+
664 std::nullopt,
+
665 tfTwoAssetIfEmpty,
666 std::nullopt,
-
667 10'000,
-
668 ter(temBAD_FEE));
-
669
-
670 // Invalid tokens - bogus currency
-
671 {
-
672 auto const iss1 = Issue{Currency(0xabc), gw.id()};
-
673 auto const iss2 = Issue{Currency(0xdef), gw.id()};
-
674 ammAlice.deposit(
-
675 alice,
-
676 1'000,
-
677 std::nullopt,
+
667 std::nullopt,
+
668 10'000,
+
669 ter(temBAD_FEE));
+
670
+
671 // Invalid tokens - bogus currency
+
672 {
+
673 auto const iss1 = Issue{Currency(0xabc), gw.id()};
+
674 auto const iss2 = Issue{Currency(0xdef), gw.id()};
+
675 ammAlice.deposit(
+
676 alice,
+
677 1'000,
678 std::nullopt,
679 std::nullopt,
680 std::nullopt,
-
681 {{iss1, iss2}},
-
682 std::nullopt,
+
681 std::nullopt,
+
682 {{iss1, iss2}},
683 std::nullopt,
-
684 ter(terNO_AMM));
-
685 }
-
686
-
687 // Depositing mismatched token, invalid Asset1In.issue
-
688 ammAlice.deposit(
-
689 alice,
-
690 GBP(100),
-
691 std::nullopt,
+
684 std::nullopt,
+
685 ter(terNO_AMM));
+
686 }
+
687
+
688 // Depositing mismatched token, invalid Asset1In.issue
+
689 ammAlice.deposit(
+
690 alice,
+
691 GBP(100),
692 std::nullopt,
693 std::nullopt,
-
694 ter(temBAD_AMM_TOKENS));
-
695
-
696 // Depositing mismatched token, invalid Asset2In.issue
-
697 ammAlice.deposit(
-
698 alice,
-
699 USD(100),
-
700 GBP(100),
-
701 std::nullopt,
+
694 std::nullopt,
+
695 ter(temBAD_AMM_TOKENS));
+
696
+
697 // Depositing mismatched token, invalid Asset2In.issue
+
698 ammAlice.deposit(
+
699 alice,
+
700 USD(100),
+
701 GBP(100),
702 std::nullopt,
-
703 ter(temBAD_AMM_TOKENS));
-
704
-
705 // Depositing mismatched token, Asset1In.issue == Asset2In.issue
-
706 ammAlice.deposit(
-
707 alice,
-
708 USD(100),
+
703 std::nullopt,
+
704 ter(temBAD_AMM_TOKENS));
+
705
+
706 // Depositing mismatched token, Asset1In.issue == Asset2In.issue
+
707 ammAlice.deposit(
+
708 alice,
709 USD(100),
-
710 std::nullopt,
+
710 USD(100),
711 std::nullopt,
-
712 ter(temBAD_AMM_TOKENS));
-
713
-
714 // Invalid amount value
-
715 ammAlice.deposit(
-
716 alice,
-
717 USD(0),
-
718 std::nullopt,
+
712 std::nullopt,
+
713 ter(temBAD_AMM_TOKENS));
+
714
+
715 // Invalid amount value
+
716 ammAlice.deposit(
+
717 alice,
+
718 USD(0),
719 std::nullopt,
720 std::nullopt,
-
721 ter(temBAD_AMOUNT));
-
722 ammAlice.deposit(
-
723 alice,
-
724 USD(-1'000),
-
725 std::nullopt,
+
721 std::nullopt,
+
722 ter(temBAD_AMOUNT));
+
723 ammAlice.deposit(
+
724 alice,
+
725 USD(-1'000),
726 std::nullopt,
727 std::nullopt,
-
728 ter(temBAD_AMOUNT));
-
729 ammAlice.deposit(
-
730 alice,
-
731 USD(10),
-
732 std::nullopt,
-
733 USD(-1),
-
734 std::nullopt,
-
735 ter(temBAD_AMOUNT));
-
736
-
737 // Bad currency
-
738 ammAlice.deposit(
-
739 alice,
-
740 BAD(100),
-
741 std::nullopt,
+
728 std::nullopt,
+
729 ter(temBAD_AMOUNT));
+
730 ammAlice.deposit(
+
731 alice,
+
732 USD(10),
+
733 std::nullopt,
+
734 USD(-1),
+
735 std::nullopt,
+
736 ter(temBAD_AMOUNT));
+
737
+
738 // Bad currency
+
739 ammAlice.deposit(
+
740 alice,
+
741 BAD(100),
742 std::nullopt,
743 std::nullopt,
-
744 ter(temBAD_CURRENCY));
-
745
-
746 // Invalid Account
-
747 Account bad("bad");
-
748 env.memoize(bad);
-
749 ammAlice.deposit(
-
750 bad,
-
751 1'000'000,
-
752 std::nullopt,
+
744 std::nullopt,
+
745 ter(temBAD_CURRENCY));
+
746
+
747 // Invalid Account
+
748 Account bad("bad");
+
749 env.memoize(bad);
+
750 ammAlice.deposit(
+
751 bad,
+
752 1'000'000,
753 std::nullopt,
754 std::nullopt,
755 std::nullopt,
756 std::nullopt,
-
757 seq(1),
-
758 std::nullopt,
-
759 ter(terNO_ACCOUNT));
-
760
-
761 // Invalid AMM
-
762 ammAlice.deposit(
-
763 alice,
-
764 1'000,
-
765 std::nullopt,
+
757 std::nullopt,
+
758 seq(1),
+
759 std::nullopt,
+
760 ter(terNO_ACCOUNT));
+
761
+
762 // Invalid AMM
+
763 ammAlice.deposit(
+
764 alice,
+
765 1'000,
766 std::nullopt,
767 std::nullopt,
768 std::nullopt,
-
769 {{USD, GBP}},
-
770 std::nullopt,
+
769 std::nullopt,
+
770 {{USD, GBP}},
771 std::nullopt,
-
772 ter(terNO_AMM));
-
773
-
774 // Single deposit: 100000 tokens worth of USD
-
775 // Amount to deposit exceeds Max
-
776 ammAlice.deposit(
-
777 carol,
-
778 100'000,
-
779 USD(200),
-
780 std::nullopt,
+
772 std::nullopt,
+
773 ter(terNO_AMM));
+
774
+
775 // Single deposit: 100000 tokens worth of USD
+
776 // Amount to deposit exceeds Max
+
777 ammAlice.deposit(
+
778 carol,
+
779 100'000,
+
780 USD(200),
781 std::nullopt,
782 std::nullopt,
783 std::nullopt,
784 std::nullopt,
785 std::nullopt,
-
786 ter(tecAMM_FAILED));
-
787
-
788 // Single deposit: 100000 tokens worth of XRP
-
789 // Amount to deposit exceeds Max
-
790 ammAlice.deposit(
-
791 carol,
-
792 100'000,
-
793 XRP(200),
-
794 std::nullopt,
+
786 std::nullopt,
+
787 ter(tecAMM_FAILED));
+
788
+
789 // Single deposit: 100000 tokens worth of XRP
+
790 // Amount to deposit exceeds Max
+
791 ammAlice.deposit(
+
792 carol,
+
793 100'000,
+
794 XRP(200),
795 std::nullopt,
796 std::nullopt,
797 std::nullopt,
798 std::nullopt,
799 std::nullopt,
-
800 ter(tecAMM_FAILED));
-
801
-
802 // Deposit amount is invalid
-
803 // Calculated amount to deposit is 98,000,000
-
804 ammAlice.deposit(
-
805 alice,
-
806 USD(0),
-
807 std::nullopt,
-
808 STAmount{USD, 1, -1},
-
809 std::nullopt,
-
810 ter(tecUNFUNDED_AMM));
-
811 // Calculated amount is 0
-
812 ammAlice.deposit(
-
813 alice,
-
814 USD(0),
-
815 std::nullopt,
-
816 STAmount{USD, 2'000, -6},
-
817 std::nullopt,
-
818 ter(tecAMM_FAILED));
-
819
-
820 // Tiny deposit
-
821 ammAlice.deposit(
-
822 carol,
-
823 IOUAmount{1, -4},
-
824 std::nullopt,
+
800 std::nullopt,
+
801 ter(tecAMM_FAILED));
+
802
+
803 // Deposit amount is invalid
+
804 // Calculated amount to deposit is 98,000,000
+
805 ammAlice.deposit(
+
806 alice,
+
807 USD(0),
+
808 std::nullopt,
+
809 STAmount{USD, 1, -1},
+
810 std::nullopt,
+
811 ter(tecUNFUNDED_AMM));
+
812 // Calculated amount is 0
+
813 ammAlice.deposit(
+
814 alice,
+
815 USD(0),
+
816 std::nullopt,
+
817 STAmount{USD, 2'000, -6},
+
818 std::nullopt,
+
819 ter(tecAMM_FAILED));
+
820
+
821 // Tiny deposit
+
822 ammAlice.deposit(
+
823 carol,
+
824 IOUAmount{1, -4},
825 std::nullopt,
-
826 ter(temBAD_AMOUNT));
-
827 ammAlice.deposit(
-
828 carol,
-
829 STAmount{USD, 1, -12},
-
830 std::nullopt,
+
826 std::nullopt,
+
827 ter(temBAD_AMOUNT));
+
828 ammAlice.deposit(
+
829 carol,
+
830 STAmount{USD, 1, -12},
831 std::nullopt,
832 std::nullopt,
-
833 ter(tecAMM_INVALID_TOKENS));
-
834
-
835 // Deposit non-empty AMM
-
836 ammAlice.deposit(
-
837 carol,
-
838 XRP(100),
-
839 USD(100),
-
840 std::nullopt,
-
841 tfTwoAssetIfEmpty,
-
842 ter(tecAMM_NOT_EMPTY));
-
843 });
-
844
-
845 // Invalid AMM
-
846 testAMM([&](AMM& ammAlice, Env& env) {
-
847 ammAlice.withdrawAll(alice);
-
848 ammAlice.deposit(
-
849 alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM));
-
850 });
-
851
-
852 // Globally frozen asset
-
853 testAMM(
-
854 [&](AMM& ammAlice, Env& env) {
-
855 env(fset(gw, asfGlobalFreeze));
-
856 if (!features[featureAMMClawback])
-
857 // If the issuer set global freeze, the holder still can
-
858 // deposit the other non-frozen token when AMMClawback is
-
859 // not enabled.
-
860 ammAlice.deposit(carol, XRP(100));
-
861 else
-
862 // If the issuer set global freeze, the holder cannot
-
863 // deposit the other non-frozen token when AMMClawback is
-
864 // enabled.
-
865 ammAlice.deposit(
-
866 carol,
-
867 XRP(100),
-
868 std::nullopt,
+
833 std::nullopt,
+
834 ter(tecAMM_INVALID_TOKENS));
+
835
+
836 // Deposit non-empty AMM
+
837 ammAlice.deposit(
+
838 carol,
+
839 XRP(100),
+
840 USD(100),
+
841 std::nullopt,
+
842 tfTwoAssetIfEmpty,
+
843 ter(tecAMM_NOT_EMPTY));
+
844 });
+
845
+
846 // Invalid AMM
+
847 testAMM([&](AMM& ammAlice, Env& env) {
+
848 ammAlice.withdrawAll(alice);
+
849 ammAlice.deposit(
+
850 alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM));
+
851 });
+
852
+
853 // Globally frozen asset
+
854 testAMM(
+
855 [&](AMM& ammAlice, Env& env) {
+
856 env(fset(gw, asfGlobalFreeze));
+
857 if (!features[featureAMMClawback])
+
858 // If the issuer set global freeze, the holder still can
+
859 // deposit the other non-frozen token when AMMClawback is
+
860 // not enabled.
+
861 ammAlice.deposit(carol, XRP(100));
+
862 else
+
863 // If the issuer set global freeze, the holder cannot
+
864 // deposit the other non-frozen token when AMMClawback is
+
865 // enabled.
+
866 ammAlice.deposit(
+
867 carol,
+
868 XRP(100),
869 std::nullopt,
870 std::nullopt,
-
871 ter(tecFROZEN));
-
872 ammAlice.deposit(
-
873 carol,
-
874 USD(100),
-
875 std::nullopt,
+
871 std::nullopt,
+
872 ter(tecFROZEN));
+
873 ammAlice.deposit(
+
874 carol,
+
875 USD(100),
876 std::nullopt,
877 std::nullopt,
-
878 ter(tecFROZEN));
-
879 ammAlice.deposit(
-
880 carol,
-
881 1'000'000,
-
882 std::nullopt,
+
878 std::nullopt,
+
879 ter(tecFROZEN));
+
880 ammAlice.deposit(
+
881 carol,
+
882 1'000'000,
883 std::nullopt,
-
884 ter(tecFROZEN));
-
885 ammAlice.deposit(
-
886 carol,
-
887 XRP(100),
-
888 USD(100),
-
889 std::nullopt,
+
884 std::nullopt,
+
885 ter(tecFROZEN));
+
886 ammAlice.deposit(
+
887 carol,
+
888 XRP(100),
+
889 USD(100),
890 std::nullopt,
-
891 ter(tecFROZEN));
-
892 },
-
893 std::nullopt,
-
894 0,
-
895 std::nullopt,
-
896 {features});
-
897
-
898 // Individually frozen (AMM) account
-
899 testAMM(
-
900 [&](AMM& ammAlice, Env& env) {
-
901 env(trust(gw, carol["USD"](0), tfSetFreeze));
-
902 env.close();
-
903 if (!features[featureAMMClawback])
-
904 // Can deposit non-frozen token if AMMClawback is not
-
905 // enabled
-
906 ammAlice.deposit(carol, XRP(100));
-
907 else
-
908 // Cannot deposit non-frozen token if the other token is
-
909 // frozen when AMMClawback is enabled
-
910 ammAlice.deposit(
-
911 carol,
-
912 XRP(100),
-
913 std::nullopt,
+
891 std::nullopt,
+
892 ter(tecFROZEN));
+
893 },
+
894 std::nullopt,
+
895 0,
+
896 std::nullopt,
+
897 {features});
+
898
+
899 // Individually frozen (AMM) account
+
900 testAMM(
+
901 [&](AMM& ammAlice, Env& env) {
+
902 env(trust(gw, carol["USD"](0), tfSetFreeze));
+
903 env.close();
+
904 if (!features[featureAMMClawback])
+
905 // Can deposit non-frozen token if AMMClawback is not
+
906 // enabled
+
907 ammAlice.deposit(carol, XRP(100));
+
908 else
+
909 // Cannot deposit non-frozen token if the other token is
+
910 // frozen when AMMClawback is enabled
+
911 ammAlice.deposit(
+
912 carol,
+
913 XRP(100),
914 std::nullopt,
915 std::nullopt,
-
916 ter(tecFROZEN));
-
917
-
918 ammAlice.deposit(
-
919 carol,
-
920 1'000'000,
-
921 std::nullopt,
+
916 std::nullopt,
+
917 ter(tecFROZEN));
+
918
+
919 ammAlice.deposit(
+
920 carol,
+
921 1'000'000,
922 std::nullopt,
-
923 ter(tecFROZEN));
-
924 ammAlice.deposit(
-
925 carol,
-
926 USD(100),
-
927 std::nullopt,
+
923 std::nullopt,
+
924 ter(tecFROZEN));
+
925 ammAlice.deposit(
+
926 carol,
+
927 USD(100),
928 std::nullopt,
929 std::nullopt,
-
930 ter(tecFROZEN));
-
931 env(trust(gw, carol["USD"](0), tfClearFreeze));
-
932 // Individually frozen AMM
-
933 env(trust(
-
934 gw,
-
935 STAmount{
-
936 Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
-
937 tfSetFreeze));
-
938 env.close();
-
939 // Can deposit non-frozen token
-
940 ammAlice.deposit(carol, XRP(100));
-
941 ammAlice.deposit(
-
942 carol,
-
943 1'000'000,
-
944 std::nullopt,
+
930 std::nullopt,
+
931 ter(tecFROZEN));
+
932 env(trust(gw, carol["USD"](0), tfClearFreeze));
+
933 // Individually frozen AMM
+
934 env(trust(
+
935 gw,
+
936 STAmount{
+
937 Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
+
938 tfSetFreeze));
+
939 env.close();
+
940 // Can deposit non-frozen token
+
941 ammAlice.deposit(carol, XRP(100));
+
942 ammAlice.deposit(
+
943 carol,
+
944 1'000'000,
945 std::nullopt,
-
946 ter(tecFROZEN));
-
947 ammAlice.deposit(
-
948 carol,
-
949 USD(100),
-
950 std::nullopt,
+
946 std::nullopt,
+
947 ter(tecFROZEN));
+
948 ammAlice.deposit(
+
949 carol,
+
950 USD(100),
951 std::nullopt,
952 std::nullopt,
-
953 ter(tecFROZEN));
-
954 },
-
955 std::nullopt,
-
956 0,
-
957 std::nullopt,
-
958 {features});
-
959
-
960 // Individually frozen (AMM) account with IOU/IOU AMM
-
961 testAMM(
-
962 [&](AMM& ammAlice, Env& env) {
-
963 env(trust(gw, carol["USD"](0), tfSetFreeze));
-
964 env(trust(gw, carol["BTC"](0), tfSetFreeze));
-
965 env.close();
-
966 ammAlice.deposit(
-
967 carol,
-
968 1'000'000,
-
969 std::nullopt,
+
953 std::nullopt,
+
954 ter(tecFROZEN));
+
955 },
+
956 std::nullopt,
+
957 0,
+
958 std::nullopt,
+
959 {features});
+
960
+
961 // Individually frozen (AMM) account with IOU/IOU AMM
+
962 testAMM(
+
963 [&](AMM& ammAlice, Env& env) {
+
964 env(trust(gw, carol["USD"](0), tfSetFreeze));
+
965 env(trust(gw, carol["BTC"](0), tfSetFreeze));
+
966 env.close();
+
967 ammAlice.deposit(
+
968 carol,
+
969 1'000'000,
970 std::nullopt,
-
971 ter(tecFROZEN));
-
972 ammAlice.deposit(
-
973 carol,
-
974 USD(100),
-
975 std::nullopt,
+
971 std::nullopt,
+
972 ter(tecFROZEN));
+
973 ammAlice.deposit(
+
974 carol,
+
975 USD(100),
976 std::nullopt,
977 std::nullopt,
-
978 ter(tecFROZEN));
-
979 env(trust(gw, carol["USD"](0), tfClearFreeze));
-
980 // Individually frozen AMM
-
981 env(trust(
-
982 gw,
-
983 STAmount{
-
984 Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
-
985 tfSetFreeze));
-
986 env.close();
-
987 // Cannot deposit non-frozen token
-
988 ammAlice.deposit(
-
989 carol,
-
990 1'000'000,
-
991 std::nullopt,
+
978 std::nullopt,
+
979 ter(tecFROZEN));
+
980 env(trust(gw, carol["USD"](0), tfClearFreeze));
+
981 // Individually frozen AMM
+
982 env(trust(
+
983 gw,
+
984 STAmount{
+
985 Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
+
986 tfSetFreeze));
+
987 env.close();
+
988 // Cannot deposit non-frozen token
+
989 ammAlice.deposit(
+
990 carol,
+
991 1'000'000,
992 std::nullopt,
-
993 ter(tecFROZEN));
-
994 ammAlice.deposit(
-
995 carol,
-
996 USD(100),
-
997 BTC(0.01),
-
998 std::nullopt,
+
993 std::nullopt,
+
994 ter(tecFROZEN));
+
995 ammAlice.deposit(
+
996 carol,
+
997 USD(100),
+
998 BTC(0.01),
999 std::nullopt,
-
1000 ter(tecFROZEN));
-
1001 },
-
1002 {{USD(20'000), BTC(0.5)}});
-
1003
-
1004 // Deposit unauthorized token.
-
1005 {
-
1006 Env env(*this, features);
-
1007 env.fund(XRP(1000), gw, alice, bob);
-
1008 env(fset(gw, asfRequireAuth));
-
1009 env.close();
-
1010 env(trust(gw, alice["USD"](100)), txflags(tfSetfAuth));
-
1011 env(trust(alice, gw["USD"](20)));
-
1012 env.close();
-
1013 env(pay(gw, alice, gw["USD"](10)));
-
1014 env.close();
-
1015 env(trust(gw, bob["USD"](100)));
-
1016 env.close();
-
1017
-
1018 AMM amm(env, alice, XRP(10), gw["USD"](10), ter(tesSUCCESS));
-
1019 env.close();
-
1020
-
1021 if (features[featureAMMClawback])
-
1022 // if featureAMMClawback is enabled, bob can not deposit XRP
-
1023 // because he's not authorized to hold the paired token
-
1024 // gw["USD"].
-
1025 amm.deposit(
-
1026 bob,
-
1027 XRP(10),
-
1028 std::nullopt,
+
1000 std::nullopt,
+
1001 ter(tecFROZEN));
+
1002 },
+
1003 {{USD(20'000), BTC(0.5)}});
+
1004
+
1005 // Deposit unauthorized token.
+
1006 {
+
1007 Env env(*this, features);
+
1008 env.fund(XRP(1000), gw, alice, bob);
+
1009 env(fset(gw, asfRequireAuth));
+
1010 env.close();
+
1011 env(trust(gw, alice["USD"](100)), txflags(tfSetfAuth));
+
1012 env(trust(alice, gw["USD"](20)));
+
1013 env.close();
+
1014 env(pay(gw, alice, gw["USD"](10)));
+
1015 env.close();
+
1016 env(trust(gw, bob["USD"](100)));
+
1017 env.close();
+
1018
+
1019 AMM amm(env, alice, XRP(10), gw["USD"](10), ter(tesSUCCESS));
+
1020 env.close();
+
1021
+
1022 if (features[featureAMMClawback])
+
1023 // if featureAMMClawback is enabled, bob can not deposit XRP
+
1024 // because he's not authorized to hold the paired token
+
1025 // gw["USD"].
+
1026 amm.deposit(
+
1027 bob,
+
1028 XRP(10),
1029 std::nullopt,
1030 std::nullopt,
-
1031 ter(tecNO_AUTH));
-
1032 else
-
1033 amm.deposit(
-
1034 bob,
-
1035 XRP(10),
-
1036 std::nullopt,
+
1031 std::nullopt,
+
1032 ter(tecNO_AUTH));
+
1033 else
+
1034 amm.deposit(
+
1035 bob,
+
1036 XRP(10),
1037 std::nullopt,
1038 std::nullopt,
-
1039 ter(tesSUCCESS));
-
1040 }
-
1041
-
1042 // Insufficient XRP balance
-
1043 testAMM([&](AMM& ammAlice, Env& env) {
-
1044 env.fund(XRP(1'000), bob);
-
1045 env.close();
-
1046 // Adds LPT trustline
-
1047 ammAlice.deposit(bob, XRP(10));
-
1048 ammAlice.deposit(
-
1049 bob,
-
1050 XRP(1'000),
-
1051 std::nullopt,
+
1039 std::nullopt,
+
1040 ter(tesSUCCESS));
+
1041 }
+
1042
+
1043 // Insufficient XRP balance
+
1044 testAMM([&](AMM& ammAlice, Env& env) {
+
1045 env.fund(XRP(1'000), bob);
+
1046 env.close();
+
1047 // Adds LPT trustline
+
1048 ammAlice.deposit(bob, XRP(10));
+
1049 ammAlice.deposit(
+
1050 bob,
+
1051 XRP(1'000),
1052 std::nullopt,
1053 std::nullopt,
-
1054 ter(tecUNFUNDED_AMM));
-
1055 });
-
1056
-
1057 // Insufficient USD balance
-
1058 testAMM([&](AMM& ammAlice, Env& env) {
-
1059 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
-
1060 env.close();
-
1061 ammAlice.deposit(
-
1062 bob,
-
1063 USD(1'001),
-
1064 std::nullopt,
+
1054 std::nullopt,
+
1055 ter(tecUNFUNDED_AMM));
+
1056 });
+
1057
+
1058 // Insufficient USD balance
+
1059 testAMM([&](AMM& ammAlice, Env& env) {
+
1060 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
+
1061 env.close();
+
1062 ammAlice.deposit(
+
1063 bob,
+
1064 USD(1'001),
1065 std::nullopt,
1066 std::nullopt,
-
1067 ter(tecUNFUNDED_AMM));
-
1068 });
-
1069
-
1070 // Insufficient USD balance by tokens
-
1071 testAMM([&](AMM& ammAlice, Env& env) {
-
1072 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
-
1073 env.close();
-
1074 ammAlice.deposit(
-
1075 bob,
-
1076 10'000'000,
-
1077 std::nullopt,
+
1067 std::nullopt,
+
1068 ter(tecUNFUNDED_AMM));
+
1069 });
+
1070
+
1071 // Insufficient USD balance by tokens
+
1072 testAMM([&](AMM& ammAlice, Env& env) {
+
1073 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
+
1074 env.close();
+
1075 ammAlice.deposit(
+
1076 bob,
+
1077 10'000'000,
1078 std::nullopt,
1079 std::nullopt,
1080 std::nullopt,
1081 std::nullopt,
1082 std::nullopt,
1083 std::nullopt,
-
1084 ter(tecUNFUNDED_AMM));
-
1085 });
-
1086
-
1087 // Insufficient XRP balance by tokens
-
1088 testAMM([&](AMM& ammAlice, Env& env) {
-
1089 env.fund(XRP(1'000), bob);
-
1090 env.trust(USD(100'000), bob);
-
1091 env.close();
-
1092 env(pay(gw, bob, USD(90'000)));
-
1093 env.close();
-
1094 ammAlice.deposit(
-
1095 bob,
-
1096 10'000'000,
-
1097 std::nullopt,
+
1084 std::nullopt,
+
1085 ter(tecUNFUNDED_AMM));
+
1086 });
+
1087
+
1088 // Insufficient XRP balance by tokens
+
1089 testAMM([&](AMM& ammAlice, Env& env) {
+
1090 env.fund(XRP(1'000), bob);
+
1091 env.trust(USD(100'000), bob);
+
1092 env.close();
+
1093 env(pay(gw, bob, USD(90'000)));
+
1094 env.close();
+
1095 ammAlice.deposit(
+
1096 bob,
+
1097 10'000'000,
1098 std::nullopt,
1099 std::nullopt,
1100 std::nullopt,
1101 std::nullopt,
1102 std::nullopt,
1103 std::nullopt,
-
1104 ter(tecUNFUNDED_AMM));
-
1105 });
-
1106
-
1107 // Insufficient reserve, XRP/IOU
-
1108 {
-
1109 Env env(*this);
-
1110 auto const starting_xrp =
-
1111 reserve(env, 4) + env.current()->fees().base * 4;
-
1112 env.fund(XRP(10'000), gw);
-
1113 env.fund(XRP(10'000), alice);
-
1114 env.fund(starting_xrp, carol);
-
1115 env.trust(USD(2'000), alice);
-
1116 env.trust(USD(2'000), carol);
-
1117 env.close();
-
1118 env(pay(gw, alice, USD(2'000)));
-
1119 env(pay(gw, carol, USD(2'000)));
-
1120 env.close();
-
1121 env(offer(carol, XRP(100), USD(101)));
-
1122 env(offer(carol, XRP(100), USD(102)));
-
1123 AMM ammAlice(env, alice, XRP(1'000), USD(1'000));
-
1124 ammAlice.deposit(
-
1125 carol,
-
1126 XRP(100),
-
1127 std::nullopt,
+
1104 std::nullopt,
+
1105 ter(tecUNFUNDED_AMM));
+
1106 });
+
1107
+
1108 // Insufficient reserve, XRP/IOU
+
1109 {
+
1110 Env env(*this);
+
1111 auto const starting_xrp =
+
1112 reserve(env, 4) + env.current()->fees().base * 4;
+
1113 env.fund(XRP(10'000), gw);
+
1114 env.fund(XRP(10'000), alice);
+
1115 env.fund(starting_xrp, carol);
+
1116 env.trust(USD(2'000), alice);
+
1117 env.trust(USD(2'000), carol);
+
1118 env.close();
+
1119 env(pay(gw, alice, USD(2'000)));
+
1120 env(pay(gw, carol, USD(2'000)));
+
1121 env.close();
+
1122 env(offer(carol, XRP(100), USD(101)));
+
1123 env(offer(carol, XRP(100), USD(102)));
+
1124 AMM ammAlice(env, alice, XRP(1'000), USD(1'000));
+
1125 ammAlice.deposit(
+
1126 carol,
+
1127 XRP(100),
1128 std::nullopt,
1129 std::nullopt,
-
1130 ter(tecINSUF_RESERVE_LINE));
-
1131
-
1132 env(offer(carol, XRP(100), USD(103)));
-
1133 ammAlice.deposit(
-
1134 carol,
-
1135 USD(100),
-
1136 std::nullopt,
+
1130 std::nullopt,
+
1131 ter(tecINSUF_RESERVE_LINE));
+
1132
+
1133 env(offer(carol, XRP(100), USD(103)));
+
1134 ammAlice.deposit(
+
1135 carol,
+
1136 USD(100),
1137 std::nullopt,
1138 std::nullopt,
-
1139 ter(tecINSUF_RESERVE_LINE));
-
1140 }
-
1141
-
1142 // Insufficient reserve, IOU/IOU
-
1143 {
-
1144 Env env(*this);
-
1145 auto const starting_xrp =
-
1146 reserve(env, 4) + env.current()->fees().base * 4;
-
1147 env.fund(XRP(10'000), gw);
-
1148 env.fund(XRP(10'000), alice);
-
1149 env.fund(starting_xrp, carol);
-
1150 env.trust(USD(2'000), alice);
-
1151 env.trust(EUR(2'000), alice);
-
1152 env.trust(USD(2'000), carol);
-
1153 env.trust(EUR(2'000), carol);
-
1154 env.close();
-
1155 env(pay(gw, alice, USD(2'000)));
-
1156 env(pay(gw, alice, EUR(2'000)));
-
1157 env(pay(gw, carol, USD(2'000)));
-
1158 env(pay(gw, carol, EUR(2'000)));
-
1159 env.close();
-
1160 env(offer(carol, XRP(100), USD(101)));
-
1161 env(offer(carol, XRP(100), USD(102)));
-
1162 AMM ammAlice(env, alice, XRP(1'000), USD(1'000));
-
1163 ammAlice.deposit(
-
1164 carol,
-
1165 XRP(100),
-
1166 std::nullopt,
+
1139 std::nullopt,
+
1140 ter(tecINSUF_RESERVE_LINE));
+
1141 }
+
1142
+
1143 // Insufficient reserve, IOU/IOU
+
1144 {
+
1145 Env env(*this);
+
1146 auto const starting_xrp =
+
1147 reserve(env, 4) + env.current()->fees().base * 4;
+
1148 env.fund(XRP(10'000), gw);
+
1149 env.fund(XRP(10'000), alice);
+
1150 env.fund(starting_xrp, carol);
+
1151 env.trust(USD(2'000), alice);
+
1152 env.trust(EUR(2'000), alice);
+
1153 env.trust(USD(2'000), carol);
+
1154 env.trust(EUR(2'000), carol);
+
1155 env.close();
+
1156 env(pay(gw, alice, USD(2'000)));
+
1157 env(pay(gw, alice, EUR(2'000)));
+
1158 env(pay(gw, carol, USD(2'000)));
+
1159 env(pay(gw, carol, EUR(2'000)));
+
1160 env.close();
+
1161 env(offer(carol, XRP(100), USD(101)));
+
1162 env(offer(carol, XRP(100), USD(102)));
+
1163 AMM ammAlice(env, alice, XRP(1'000), USD(1'000));
+
1164 ammAlice.deposit(
+
1165 carol,
+
1166 XRP(100),
1167 std::nullopt,
1168 std::nullopt,
-
1169 ter(tecINSUF_RESERVE_LINE));
-
1170 }
-
1171
-
1172 // Invalid min
-
1173 testAMM([&](AMM& ammAlice, Env& env) {
-
1174 // min tokens can't be <= zero
-
1175 ammAlice.deposit(
-
1176 carol, 0, XRP(100), tfSingleAsset, ter(temBAD_AMM_TOKENS));
-
1177 ammAlice.deposit(
-
1178 carol, -1, XRP(100), tfSingleAsset, ter(temBAD_AMM_TOKENS));
-
1179 ammAlice.deposit(
-
1180 carol,
-
1181 0,
-
1182 XRP(100),
-
1183 USD(100),
-
1184 std::nullopt,
-
1185 tfTwoAsset,
-
1186 std::nullopt,
+
1169 std::nullopt,
+
1170 ter(tecINSUF_RESERVE_LINE));
+
1171 }
+
1172
+
1173 // Invalid min
+
1174 testAMM([&](AMM& ammAlice, Env& env) {
+
1175 // min tokens can't be <= zero
+
1176 ammAlice.deposit(
+
1177 carol, 0, XRP(100), tfSingleAsset, ter(temBAD_AMM_TOKENS));
+
1178 ammAlice.deposit(
+
1179 carol, -1, XRP(100), tfSingleAsset, ter(temBAD_AMM_TOKENS));
+
1180 ammAlice.deposit(
+
1181 carol,
+
1182 0,
+
1183 XRP(100),
+
1184 USD(100),
+
1185 std::nullopt,
+
1186 tfTwoAsset,
1187 std::nullopt,
1188 std::nullopt,
-
1189 ter(temBAD_AMM_TOKENS));
-
1190 // min amounts can't be <= zero
-
1191 ammAlice.deposit(
-
1192 carol,
-
1193 1'000,
-
1194 XRP(0),
-
1195 USD(100),
-
1196 std::nullopt,
-
1197 tfTwoAsset,
-
1198 std::nullopt,
+
1189 std::nullopt,
+
1190 ter(temBAD_AMM_TOKENS));
+
1191 // min amounts can't be <= zero
+
1192 ammAlice.deposit(
+
1193 carol,
+
1194 1'000,
+
1195 XRP(0),
+
1196 USD(100),
+
1197 std::nullopt,
+
1198 tfTwoAsset,
1199 std::nullopt,
1200 std::nullopt,
-
1201 ter(temBAD_AMOUNT));
-
1202 ammAlice.deposit(
-
1203 carol,
-
1204 1'000,
-
1205 XRP(100),
-
1206 USD(-1),
-
1207 std::nullopt,
-
1208 tfTwoAsset,
-
1209 std::nullopt,
+
1201 std::nullopt,
+
1202 ter(temBAD_AMOUNT));
+
1203 ammAlice.deposit(
+
1204 carol,
+
1205 1'000,
+
1206 XRP(100),
+
1207 USD(-1),
+
1208 std::nullopt,
+
1209 tfTwoAsset,
1210 std::nullopt,
1211 std::nullopt,
-
1212 ter(temBAD_AMOUNT));
-
1213 // min amount bad currency
-
1214 ammAlice.deposit(
-
1215 carol,
-
1216 1'000,
-
1217 XRP(100),
-
1218 BAD(100),
-
1219 std::nullopt,
-
1220 tfTwoAsset,
-
1221 std::nullopt,
+
1212 std::nullopt,
+
1213 ter(temBAD_AMOUNT));
+
1214 // min amount bad currency
+
1215 ammAlice.deposit(
+
1216 carol,
+
1217 1'000,
+
1218 XRP(100),
+
1219 BAD(100),
+
1220 std::nullopt,
+
1221 tfTwoAsset,
1222 std::nullopt,
1223 std::nullopt,
-
1224 ter(temBAD_CURRENCY));
-
1225 // min amount bad token pair
-
1226 ammAlice.deposit(
-
1227 carol,
-
1228 1'000,
-
1229 XRP(100),
+
1224 std::nullopt,
+
1225 ter(temBAD_CURRENCY));
+
1226 // min amount bad token pair
+
1227 ammAlice.deposit(
+
1228 carol,
+
1229 1'000,
1230 XRP(100),
-
1231 std::nullopt,
-
1232 tfTwoAsset,
-
1233 std::nullopt,
+
1231 XRP(100),
+
1232 std::nullopt,
+
1233 tfTwoAsset,
1234 std::nullopt,
1235 std::nullopt,
-
1236 ter(temBAD_AMM_TOKENS));
-
1237 ammAlice.deposit(
-
1238 carol,
-
1239 1'000,
-
1240 XRP(100),
-
1241 GBP(100),
-
1242 std::nullopt,
-
1243 tfTwoAsset,
-
1244 std::nullopt,
+
1236 std::nullopt,
+
1237 ter(temBAD_AMM_TOKENS));
+
1238 ammAlice.deposit(
+
1239 carol,
+
1240 1'000,
+
1241 XRP(100),
+
1242 GBP(100),
+
1243 std::nullopt,
+
1244 tfTwoAsset,
1245 std::nullopt,
1246 std::nullopt,
-
1247 ter(temBAD_AMM_TOKENS));
-
1248 });
-
1249
-
1250 // Min deposit
-
1251 testAMM([&](AMM& ammAlice, Env& env) {
-
1252 // Equal deposit by tokens
-
1253 ammAlice.deposit(
-
1254 carol,
-
1255 1'000'000,
-
1256 XRP(1'000),
-
1257 USD(1'001),
-
1258 std::nullopt,
-
1259 tfLPToken,
-
1260 std::nullopt,
+
1247 std::nullopt,
+
1248 ter(temBAD_AMM_TOKENS));
+
1249 });
+
1250
+
1251 // Min deposit
+
1252 testAMM([&](AMM& ammAlice, Env& env) {
+
1253 // Equal deposit by tokens
+
1254 ammAlice.deposit(
+
1255 carol,
+
1256 1'000'000,
+
1257 XRP(1'000),
+
1258 USD(1'001),
+
1259 std::nullopt,
+
1260 tfLPToken,
1261 std::nullopt,
1262 std::nullopt,
-
1263 ter(tecAMM_FAILED));
-
1264 ammAlice.deposit(
-
1265 carol,
-
1266 1'000'000,
-
1267 XRP(1'001),
-
1268 USD(1'000),
-
1269 std::nullopt,
-
1270 tfLPToken,
-
1271 std::nullopt,
+
1263 std::nullopt,
+
1264 ter(tecAMM_FAILED));
+
1265 ammAlice.deposit(
+
1266 carol,
+
1267 1'000'000,
+
1268 XRP(1'001),
+
1269 USD(1'000),
+
1270 std::nullopt,
+
1271 tfLPToken,
1272 std::nullopt,
1273 std::nullopt,
-
1274 ter(tecAMM_FAILED));
-
1275 // Equal deposit by asset
-
1276 ammAlice.deposit(
-
1277 carol,
-
1278 100'001,
-
1279 XRP(100),
-
1280 USD(100),
-
1281 std::nullopt,
-
1282 tfTwoAsset,
-
1283 std::nullopt,
+
1274 std::nullopt,
+
1275 ter(tecAMM_FAILED));
+
1276 // Equal deposit by asset
+
1277 ammAlice.deposit(
+
1278 carol,
+
1279 100'001,
+
1280 XRP(100),
+
1281 USD(100),
+
1282 std::nullopt,
+
1283 tfTwoAsset,
1284 std::nullopt,
1285 std::nullopt,
-
1286 ter(tecAMM_FAILED));
-
1287 // Single deposit by asset
-
1288 ammAlice.deposit(
-
1289 carol,
-
1290 488'090,
-
1291 XRP(1'000),
-
1292 std::nullopt,
+
1286 std::nullopt,
+
1287 ter(tecAMM_FAILED));
+
1288 // Single deposit by asset
+
1289 ammAlice.deposit(
+
1290 carol,
+
1291 488'090,
+
1292 XRP(1'000),
1293 std::nullopt,
-
1294 tfSingleAsset,
-
1295 std::nullopt,
+
1294 std::nullopt,
+
1295 tfSingleAsset,
1296 std::nullopt,
1297 std::nullopt,
-
1298 ter(tecAMM_FAILED));
-
1299 });
-
1300 }
-
1301
-
1302 void
-
1303 testDeposit()
-
1304 {
-
1305 testcase("Deposit");
-
1306
-
1307 using namespace jtx;
-
1308
-
1309 // Equal deposit: 1000000 tokens, 10% of the current pool
-
1310 testAMM([&](AMM& ammAlice, Env& env) {
-
1311 auto const baseFee = env.current()->fees().base;
-
1312 ammAlice.deposit(carol, 1'000'000);
-
1313 BEAST_EXPECT(ammAlice.expectBalances(
-
1314 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
1315 // 30,000 less deposited 1,000
-
1316 BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
-
1317 // 30,000 less deposited 1,000 and 10 drops tx fee
-
1318 BEAST_EXPECT(expectLedgerEntryRoot(
-
1319 env, carol, XRPAmount{29'000'000'000 - baseFee}));
-
1320 });
-
1321
-
1322 // equal asset deposit: unit test to exercise the rounding-down of
-
1323 // LPTokens in the AMMHelpers.cpp: adjustLPTokens calculations
-
1324 // The LPTokens need to have 16 significant digits and a fractional part
-
1325 for (const Number deltaLPTokens :
-
1326 {Number{UINT64_C(100000'0000000009), -10},
-
1327 Number{UINT64_C(100000'0000000001), -10}})
-
1328 {
-
1329 testAMM([&](AMM& ammAlice, Env& env) {
-
1330 // initial LPToken balance
-
1331 IOUAmount const initLPToken = ammAlice.getLPTokensBalance();
-
1332 const IOUAmount newLPTokens{
-
1333 deltaLPTokens.mantissa(), deltaLPTokens.exponent()};
-
1334
-
1335 // carol performs a two-asset deposit
-
1336 ammAlice.deposit(
-
1337 DepositArg{.account = carol, .tokens = newLPTokens});
-
1338
-
1339 IOUAmount const finalLPToken = ammAlice.getLPTokensBalance();
-
1340
-
1341 // Change in behavior due to rounding down of LPTokens:
-
1342 // there is a decrease in the observed return of LPTokens --
-
1343 // Inputs Number{UINT64_C(100000'0000000001), -10} and
-
1344 // Number{UINT64_C(100000'0000000009), -10} are both rounded
-
1345 // down to 1e5
-
1346 BEAST_EXPECT((finalLPToken - initLPToken == IOUAmount{1, 5}));
-
1347 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
-
1348
-
1349 // fraction of newLPTokens/(existing LPToken balance). The
-
1350 // existing LPToken balance is 1e7
-
1351 const Number fr = deltaLPTokens / 1e7;
-
1352
-
1353 // The below equations are based on Equation 1, 2 from XLS-30d
-
1354 // specification, Section: 2.3.1.2
-
1355 const Number deltaXRP = fr * 1e10;
-
1356 const Number deltaUSD = fr * 1e4;
-
1357
-
1358 const STAmount depositUSD =
-
1359 STAmount{USD, deltaUSD.mantissa(), deltaUSD.exponent()};
-
1360
-
1361 const STAmount depositXRP =
-
1362 STAmount{XRP, deltaXRP.mantissa(), deltaXRP.exponent()};
-
1363
-
1364 // initial LPTokens (1e7) + newLPTokens
-
1365 BEAST_EXPECT(ammAlice.expectBalances(
-
1366 XRP(10'000) + depositXRP,
-
1367 USD(10'000) + depositUSD,
-
1368 IOUAmount{1, 7} + newLPTokens));
-
1369
-
1370 // 30,000 less deposited depositUSD
-
1371 BEAST_EXPECT(expectLine(env, carol, USD(30'000) - depositUSD));
-
1372 // 30,000 less deposited depositXRP and 10 drops tx fee
-
1373 BEAST_EXPECT(expectLedgerEntryRoot(
-
1374 env, carol, XRP(30'000) - depositXRP - txfee(env, 1)));
-
1375 });
-
1376 }
-
1377
-
1378 // Equal limit deposit: deposit USD100 and XRP proportionally
-
1379 // to the pool composition not to exceed 100XRP. If the amount
-
1380 // exceeds 100XRP then deposit 100XRP and USD proportionally
-
1381 // to the pool composition not to exceed 100USD. Fail if exceeded.
-
1382 // Deposit 100USD/100XRP
-
1383 testAMM([&](AMM& ammAlice, Env&) {
-
1384 ammAlice.deposit(carol, USD(100), XRP(100));
-
1385 BEAST_EXPECT(ammAlice.expectBalances(
-
1386 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
-
1387 });
-
1388
-
1389 // Equal limit deposit.
-
1390 // Try to deposit 200USD/100XRP. Is truncated to 100USD/100XRP.
-
1391 testAMM([&](AMM& ammAlice, Env&) {
-
1392 ammAlice.deposit(carol, USD(200), XRP(100));
-
1393 BEAST_EXPECT(ammAlice.expectBalances(
-
1394 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
-
1395 });
-
1396 // Try to deposit 100USD/200XRP. Is truncated to 100USD/100XRP.
-
1397 testAMM([&](AMM& ammAlice, Env&) {
-
1398 ammAlice.deposit(carol, USD(100), XRP(200));
-
1399 BEAST_EXPECT(ammAlice.expectBalances(
-
1400 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
-
1401 });
-
1402
-
1403 // Single deposit: 1000 USD
-
1404 testAMM([&](AMM& ammAlice, Env&) {
-
1405 ammAlice.deposit(carol, USD(1'000));
-
1406 BEAST_EXPECT(ammAlice.expectBalances(
-
1407 XRP(10'000),
-
1408 STAmount{USD, UINT64_C(10'999'99999999999), -11},
-
1409 IOUAmount{10'488'088'48170151, -8}));
-
1410 });
-
1411
-
1412 // Single deposit: 1000 XRP
-
1413 testAMM([&](AMM& ammAlice, Env&) {
-
1414 ammAlice.deposit(carol, XRP(1'000));
-
1415 BEAST_EXPECT(ammAlice.expectBalances(
-
1416 XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
-
1417 });
-
1418
-
1419 // Single deposit: 100000 tokens worth of USD
-
1420 testAMM([&](AMM& ammAlice, Env&) {
-
1421 ammAlice.deposit(carol, 100000, USD(205));
-
1422 BEAST_EXPECT(ammAlice.expectBalances(
-
1423 XRP(10'000), USD(10'201), IOUAmount{10'100'000, 0}));
-
1424 });
-
1425
-
1426 // Single deposit: 100000 tokens worth of XRP
-
1427 testAMM([&](AMM& ammAlice, Env&) {
-
1428 ammAlice.deposit(carol, 100'000, XRP(205));
-
1429 BEAST_EXPECT(ammAlice.expectBalances(
-
1430 XRP(10'201), USD(10'000), IOUAmount{10'100'000, 0}));
-
1431 });
-
1432
-
1433 // Single deposit with EP not exceeding specified:
-
1434 // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut)
-
1435 testAMM([&](AMM& ammAlice, Env&) {
-
1436 ammAlice.deposit(
-
1437 carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
-
1438 BEAST_EXPECT(ammAlice.expectBalances(
-
1439 XRP(10'000),
-
1440 STAmount{USD, UINT64_C(10'999'99999999999), -11},
-
1441 IOUAmount{10'488'088'48170151, -8}));
-
1442 });
-
1443
-
1444 // Single deposit with EP not exceeding specified:
-
1445 // 100USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
-
1446 testAMM([&](AMM& ammAlice, Env&) {
-
1447 ammAlice.deposit(
-
1448 carol, USD(100), std::nullopt, STAmount{USD, 2004, -6});
-
1449 BEAST_EXPECT(ammAlice.expectBalances(
-
1450 XRP(10'000),
-
1451 STAmount{USD, 10'080'16, -2},
-
1452 IOUAmount{10'040'000, 0}));
-
1453 });
-
1454
-
1455 // Single deposit with EP not exceeding specified:
-
1456 // 0USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
-
1457 testAMM([&](AMM& ammAlice, Env&) {
-
1458 ammAlice.deposit(
-
1459 carol, USD(0), std::nullopt, STAmount{USD, 2004, -6});
-
1460 BEAST_EXPECT(ammAlice.expectBalances(
-
1461 XRP(10'000),
-
1462 STAmount{USD, 10'080'16, -2},
-
1463 IOUAmount{10'040'000, 0}));
-
1464 });
-
1465
-
1466 // IOU to IOU + transfer fee
-
1467 {
-
1468 Env env{*this};
-
1469 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
-
1470 env(rate(gw, 1.25));
-
1471 env.close();
-
1472 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
-
1473 BEAST_EXPECT(ammAlice.expectBalances(
-
1474 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
-
1475 BEAST_EXPECT(expectLine(env, alice, USD(0)));
-
1476 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
-
1477 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
-
1478 // no transfer fee on deposit
-
1479 ammAlice.deposit(carol, 10);
-
1480 BEAST_EXPECT(ammAlice.expectBalances(
-
1481 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
-
1482 BEAST_EXPECT(expectLine(env, carol, USD(0)));
-
1483 BEAST_EXPECT(expectLine(env, carol, BTC(0)));
-
1484 }
-
1485
-
1486 // Tiny deposits
-
1487 testAMM([&](AMM& ammAlice, Env&) {
-
1488 ammAlice.deposit(carol, IOUAmount{1, -3});
-
1489 BEAST_EXPECT(ammAlice.expectBalances(
-
1490 XRPAmount{10'000'000'001},
-
1491 STAmount{USD, UINT64_C(10'000'000001), -6},
-
1492 IOUAmount{10'000'000'001, -3}));
-
1493 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1, -3}));
-
1494 });
-
1495 testAMM([&](AMM& ammAlice, Env&) {
-
1496 ammAlice.deposit(carol, XRPAmount{1});
-
1497 BEAST_EXPECT(ammAlice.expectBalances(
-
1498 XRPAmount{10'000'000'001},
-
1499 USD(10'000),
-
1500 IOUAmount{1'000'000'000049999, -8}));
-
1501 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{49999, -8}));
-
1502 });
-
1503 testAMM([&](AMM& ammAlice, Env&) {
-
1504 ammAlice.deposit(carol, STAmount{USD, 1, -10});
-
1505 BEAST_EXPECT(ammAlice.expectBalances(
-
1506 XRP(10'000),
-
1507 STAmount{USD, UINT64_C(10'000'00000000008), -11},
-
1508 IOUAmount{10'000'000'00000004, -8}));
-
1509 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4, -8}));
-
1510 });
-
1511
-
1512 // Issuer create/deposit
-
1513 {
-
1514 Env env(*this);
-
1515 env.fund(XRP(30000), gw);
-
1516 AMM ammGw(env, gw, XRP(10'000), USD(10'000));
-
1517 BEAST_EXPECT(
-
1518 ammGw.expectBalances(XRP(10'000), USD(10'000), ammGw.tokens()));
-
1519 ammGw.deposit(gw, 1'000'000);
-
1520 BEAST_EXPECT(ammGw.expectBalances(
-
1521 XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
-
1522 ammGw.deposit(gw, USD(1'000));
-
1523 BEAST_EXPECT(ammGw.expectBalances(
-
1524 XRP(11'000),
-
1525 STAmount{USD, UINT64_C(11'999'99999999998), -11},
-
1526 IOUAmount{11'489'125'29307605, -8}));
-
1527 }
-
1528
-
1529 // Issuer deposit
-
1530 testAMM([&](AMM& ammAlice, Env& env) {
-
1531 ammAlice.deposit(gw, 1'000'000);
-
1532 BEAST_EXPECT(ammAlice.expectBalances(
-
1533 XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
-
1534 ammAlice.deposit(gw, USD(1'000));
-
1535 BEAST_EXPECT(ammAlice.expectBalances(
-
1536 XRP(11'000),
-
1537 STAmount{USD, UINT64_C(11'999'99999999998), -11},
-
1538 IOUAmount{11'489'125'29307605, -8}));
-
1539 });
-
1540
-
1541 // Min deposit
-
1542 testAMM([&](AMM& ammAlice, Env& env) {
-
1543 // Equal deposit by tokens
-
1544 ammAlice.deposit(
-
1545 carol,
-
1546 1'000'000,
-
1547 XRP(1'000),
-
1548 USD(1'000),
-
1549 std::nullopt,
-
1550 tfLPToken,
-
1551 std::nullopt,
-
1552 std::nullopt);
-
1553 BEAST_EXPECT(ammAlice.expectBalances(
-
1554 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
1555 });
-
1556 testAMM([&](AMM& ammAlice, Env& env) {
-
1557 // Equal deposit by asset
-
1558 ammAlice.deposit(
-
1559 carol,
-
1560 1'000'000,
-
1561 XRP(1'000),
-
1562 USD(1'000),
-
1563 std::nullopt,
-
1564 tfTwoAsset,
-
1565 std::nullopt,
-
1566 std::nullopt);
-
1567 BEAST_EXPECT(ammAlice.expectBalances(
-
1568 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
1569 });
-
1570 testAMM([&](AMM& ammAlice, Env& env) {
-
1571 // Single deposit by asset
-
1572 ammAlice.deposit(
-
1573 carol,
-
1574 488'088,
-
1575 XRP(1'000),
-
1576 std::nullopt,
+
1298 std::nullopt,
+
1299 ter(tecAMM_FAILED));
+
1300 });
+
1301 }
+
1302
+
1303 void
+
1304 testDeposit()
+
1305 {
+
1306 testcase("Deposit");
+
1307
+
1308 using namespace jtx;
+
1309
+
1310 // Equal deposit: 1000000 tokens, 10% of the current pool
+
1311 testAMM([&](AMM& ammAlice, Env& env) {
+
1312 auto const baseFee = env.current()->fees().base;
+
1313 ammAlice.deposit(carol, 1'000'000);
+
1314 BEAST_EXPECT(ammAlice.expectBalances(
+
1315 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
1316 // 30,000 less deposited 1,000
+
1317 BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
+
1318 // 30,000 less deposited 1,000 and 10 drops tx fee
+
1319 BEAST_EXPECT(expectLedgerEntryRoot(
+
1320 env, carol, XRPAmount{29'000'000'000 - baseFee}));
+
1321 });
+
1322
+
1323 // equal asset deposit: unit test to exercise the rounding-down of
+
1324 // LPTokens in the AMMHelpers.cpp: adjustLPTokens calculations
+
1325 // The LPTokens need to have 16 significant digits and a fractional part
+
1326 for (const Number deltaLPTokens :
+
1327 {Number{UINT64_C(100000'0000000009), -10},
+
1328 Number{UINT64_C(100000'0000000001), -10}})
+
1329 {
+
1330 testAMM([&](AMM& ammAlice, Env& env) {
+
1331 // initial LPToken balance
+
1332 IOUAmount const initLPToken = ammAlice.getLPTokensBalance();
+
1333 const IOUAmount newLPTokens{
+
1334 deltaLPTokens.mantissa(), deltaLPTokens.exponent()};
+
1335
+
1336 // carol performs a two-asset deposit
+
1337 ammAlice.deposit(
+
1338 DepositArg{.account = carol, .tokens = newLPTokens});
+
1339
+
1340 IOUAmount const finalLPToken = ammAlice.getLPTokensBalance();
+
1341
+
1342 // Change in behavior due to rounding down of LPTokens:
+
1343 // there is a decrease in the observed return of LPTokens --
+
1344 // Inputs Number{UINT64_C(100000'0000000001), -10} and
+
1345 // Number{UINT64_C(100000'0000000009), -10} are both rounded
+
1346 // down to 1e5
+
1347 BEAST_EXPECT((finalLPToken - initLPToken == IOUAmount{1, 5}));
+
1348 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
+
1349
+
1350 // fraction of newLPTokens/(existing LPToken balance). The
+
1351 // existing LPToken balance is 1e7
+
1352 const Number fr = deltaLPTokens / 1e7;
+
1353
+
1354 // The below equations are based on Equation 1, 2 from XLS-30d
+
1355 // specification, Section: 2.3.1.2
+
1356 const Number deltaXRP = fr * 1e10;
+
1357 const Number deltaUSD = fr * 1e4;
+
1358
+
1359 const STAmount depositUSD =
+
1360 STAmount{USD, deltaUSD.mantissa(), deltaUSD.exponent()};
+
1361
+
1362 const STAmount depositXRP =
+
1363 STAmount{XRP, deltaXRP.mantissa(), deltaXRP.exponent()};
+
1364
+
1365 // initial LPTokens (1e7) + newLPTokens
+
1366 BEAST_EXPECT(ammAlice.expectBalances(
+
1367 XRP(10'000) + depositXRP,
+
1368 USD(10'000) + depositUSD,
+
1369 IOUAmount{1, 7} + newLPTokens));
+
1370
+
1371 // 30,000 less deposited depositUSD
+
1372 BEAST_EXPECT(expectLine(env, carol, USD(30'000) - depositUSD));
+
1373 // 30,000 less deposited depositXRP and 10 drops tx fee
+
1374 BEAST_EXPECT(expectLedgerEntryRoot(
+
1375 env, carol, XRP(30'000) - depositXRP - txfee(env, 1)));
+
1376 });
+
1377 }
+
1378
+
1379 // Equal limit deposit: deposit USD100 and XRP proportionally
+
1380 // to the pool composition not to exceed 100XRP. If the amount
+
1381 // exceeds 100XRP then deposit 100XRP and USD proportionally
+
1382 // to the pool composition not to exceed 100USD. Fail if exceeded.
+
1383 // Deposit 100USD/100XRP
+
1384 testAMM([&](AMM& ammAlice, Env&) {
+
1385 ammAlice.deposit(carol, USD(100), XRP(100));
+
1386 BEAST_EXPECT(ammAlice.expectBalances(
+
1387 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
+
1388 });
+
1389
+
1390 // Equal limit deposit.
+
1391 // Try to deposit 200USD/100XRP. Is truncated to 100USD/100XRP.
+
1392 testAMM([&](AMM& ammAlice, Env&) {
+
1393 ammAlice.deposit(carol, USD(200), XRP(100));
+
1394 BEAST_EXPECT(ammAlice.expectBalances(
+
1395 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
+
1396 });
+
1397 // Try to deposit 100USD/200XRP. Is truncated to 100USD/100XRP.
+
1398 testAMM([&](AMM& ammAlice, Env&) {
+
1399 ammAlice.deposit(carol, USD(100), XRP(200));
+
1400 BEAST_EXPECT(ammAlice.expectBalances(
+
1401 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
+
1402 });
+
1403
+
1404 // Single deposit: 1000 USD
+
1405 testAMM([&](AMM& ammAlice, Env&) {
+
1406 ammAlice.deposit(carol, USD(1'000));
+
1407 BEAST_EXPECT(ammAlice.expectBalances(
+
1408 XRP(10'000),
+
1409 STAmount{USD, UINT64_C(10'999'99999999999), -11},
+
1410 IOUAmount{10'488'088'48170151, -8}));
+
1411 });
+
1412
+
1413 // Single deposit: 1000 XRP
+
1414 testAMM([&](AMM& ammAlice, Env&) {
+
1415 ammAlice.deposit(carol, XRP(1'000));
+
1416 BEAST_EXPECT(ammAlice.expectBalances(
+
1417 XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
+
1418 });
+
1419
+
1420 // Single deposit: 100000 tokens worth of USD
+
1421 testAMM([&](AMM& ammAlice, Env&) {
+
1422 ammAlice.deposit(carol, 100000, USD(205));
+
1423 BEAST_EXPECT(ammAlice.expectBalances(
+
1424 XRP(10'000), USD(10'201), IOUAmount{10'100'000, 0}));
+
1425 });
+
1426
+
1427 // Single deposit: 100000 tokens worth of XRP
+
1428 testAMM([&](AMM& ammAlice, Env&) {
+
1429 ammAlice.deposit(carol, 100'000, XRP(205));
+
1430 BEAST_EXPECT(ammAlice.expectBalances(
+
1431 XRP(10'201), USD(10'000), IOUAmount{10'100'000, 0}));
+
1432 });
+
1433
+
1434 // Single deposit with EP not exceeding specified:
+
1435 // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut)
+
1436 testAMM([&](AMM& ammAlice, Env&) {
+
1437 ammAlice.deposit(
+
1438 carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
+
1439 BEAST_EXPECT(ammAlice.expectBalances(
+
1440 XRP(10'000),
+
1441 STAmount{USD, UINT64_C(10'999'99999999999), -11},
+
1442 IOUAmount{10'488'088'48170151, -8}));
+
1443 });
+
1444
+
1445 // Single deposit with EP not exceeding specified:
+
1446 // 100USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
+
1447 testAMM([&](AMM& ammAlice, Env&) {
+
1448 ammAlice.deposit(
+
1449 carol, USD(100), std::nullopt, STAmount{USD, 2004, -6});
+
1450 BEAST_EXPECT(ammAlice.expectBalances(
+
1451 XRP(10'000),
+
1452 STAmount{USD, 10'080'16, -2},
+
1453 IOUAmount{10'040'000, 0}));
+
1454 });
+
1455
+
1456 // Single deposit with EP not exceeding specified:
+
1457 // 0USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
+
1458 testAMM([&](AMM& ammAlice, Env&) {
+
1459 ammAlice.deposit(
+
1460 carol, USD(0), std::nullopt, STAmount{USD, 2004, -6});
+
1461 BEAST_EXPECT(ammAlice.expectBalances(
+
1462 XRP(10'000),
+
1463 STAmount{USD, 10'080'16, -2},
+
1464 IOUAmount{10'040'000, 0}));
+
1465 });
+
1466
+
1467 // IOU to IOU + transfer fee
+
1468 {
+
1469 Env env{*this};
+
1470 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
+
1471 env(rate(gw, 1.25));
+
1472 env.close();
+
1473 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
+
1474 BEAST_EXPECT(ammAlice.expectBalances(
+
1475 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
+
1476 BEAST_EXPECT(expectLine(env, alice, USD(0)));
+
1477 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
+
1478 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
+
1479 // no transfer fee on deposit
+
1480 ammAlice.deposit(carol, 10);
+
1481 BEAST_EXPECT(ammAlice.expectBalances(
+
1482 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
+
1483 BEAST_EXPECT(expectLine(env, carol, USD(0)));
+
1484 BEAST_EXPECT(expectLine(env, carol, BTC(0)));
+
1485 }
+
1486
+
1487 // Tiny deposits
+
1488 testAMM([&](AMM& ammAlice, Env&) {
+
1489 ammAlice.deposit(carol, IOUAmount{1, -3});
+
1490 BEAST_EXPECT(ammAlice.expectBalances(
+
1491 XRPAmount{10'000'000'001},
+
1492 STAmount{USD, UINT64_C(10'000'000001), -6},
+
1493 IOUAmount{10'000'000'001, -3}));
+
1494 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1, -3}));
+
1495 });
+
1496 testAMM([&](AMM& ammAlice, Env&) {
+
1497 ammAlice.deposit(carol, XRPAmount{1});
+
1498 BEAST_EXPECT(ammAlice.expectBalances(
+
1499 XRPAmount{10'000'000'001},
+
1500 USD(10'000),
+
1501 IOUAmount{1'000'000'000049999, -8}));
+
1502 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{49999, -8}));
+
1503 });
+
1504 testAMM([&](AMM& ammAlice, Env&) {
+
1505 ammAlice.deposit(carol, STAmount{USD, 1, -10});
+
1506 BEAST_EXPECT(ammAlice.expectBalances(
+
1507 XRP(10'000),
+
1508 STAmount{USD, UINT64_C(10'000'00000000008), -11},
+
1509 IOUAmount{10'000'000'00000004, -8}));
+
1510 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4, -8}));
+
1511 });
+
1512
+
1513 // Issuer create/deposit
+
1514 {
+
1515 Env env(*this);
+
1516 env.fund(XRP(30000), gw);
+
1517 AMM ammGw(env, gw, XRP(10'000), USD(10'000));
+
1518 BEAST_EXPECT(
+
1519 ammGw.expectBalances(XRP(10'000), USD(10'000), ammGw.tokens()));
+
1520 ammGw.deposit(gw, 1'000'000);
+
1521 BEAST_EXPECT(ammGw.expectBalances(
+
1522 XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
+
1523 ammGw.deposit(gw, USD(1'000));
+
1524 BEAST_EXPECT(ammGw.expectBalances(
+
1525 XRP(11'000),
+
1526 STAmount{USD, UINT64_C(11'999'99999999998), -11},
+
1527 IOUAmount{11'489'125'29307605, -8}));
+
1528 }
+
1529
+
1530 // Issuer deposit
+
1531 testAMM([&](AMM& ammAlice, Env& env) {
+
1532 ammAlice.deposit(gw, 1'000'000);
+
1533 BEAST_EXPECT(ammAlice.expectBalances(
+
1534 XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
+
1535 ammAlice.deposit(gw, USD(1'000));
+
1536 BEAST_EXPECT(ammAlice.expectBalances(
+
1537 XRP(11'000),
+
1538 STAmount{USD, UINT64_C(11'999'99999999998), -11},
+
1539 IOUAmount{11'489'125'29307605, -8}));
+
1540 });
+
1541
+
1542 // Min deposit
+
1543 testAMM([&](AMM& ammAlice, Env& env) {
+
1544 // Equal deposit by tokens
+
1545 ammAlice.deposit(
+
1546 carol,
+
1547 1'000'000,
+
1548 XRP(1'000),
+
1549 USD(1'000),
+
1550 std::nullopt,
+
1551 tfLPToken,
+
1552 std::nullopt,
+
1553 std::nullopt);
+
1554 BEAST_EXPECT(ammAlice.expectBalances(
+
1555 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
1556 });
+
1557 testAMM([&](AMM& ammAlice, Env& env) {
+
1558 // Equal deposit by asset
+
1559 ammAlice.deposit(
+
1560 carol,
+
1561 1'000'000,
+
1562 XRP(1'000),
+
1563 USD(1'000),
+
1564 std::nullopt,
+
1565 tfTwoAsset,
+
1566 std::nullopt,
+
1567 std::nullopt);
+
1568 BEAST_EXPECT(ammAlice.expectBalances(
+
1569 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
1570 });
+
1571 testAMM([&](AMM& ammAlice, Env& env) {
+
1572 // Single deposit by asset
+
1573 ammAlice.deposit(
+
1574 carol,
+
1575 488'088,
+
1576 XRP(1'000),
1577 std::nullopt,
-
1578 tfSingleAsset,
-
1579 std::nullopt,
-
1580 std::nullopt);
-
1581 BEAST_EXPECT(ammAlice.expectBalances(
-
1582 XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
-
1583 });
-
1584 testAMM([&](AMM& ammAlice, Env& env) {
-
1585 // Single deposit by asset
-
1586 ammAlice.deposit(
-
1587 carol,
-
1588 488'088,
-
1589 USD(1'000),
-
1590 std::nullopt,
+
1578 std::nullopt,
+
1579 tfSingleAsset,
+
1580 std::nullopt,
+
1581 std::nullopt);
+
1582 BEAST_EXPECT(ammAlice.expectBalances(
+
1583 XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
+
1584 });
+
1585 testAMM([&](AMM& ammAlice, Env& env) {
+
1586 // Single deposit by asset
+
1587 ammAlice.deposit(
+
1588 carol,
+
1589 488'088,
+
1590 USD(1'000),
1591 std::nullopt,
-
1592 tfSingleAsset,
-
1593 std::nullopt,
-
1594 std::nullopt);
-
1595 BEAST_EXPECT(ammAlice.expectBalances(
-
1596 XRP(10'000),
-
1597 STAmount{USD, UINT64_C(10'999'99999999999), -11},
-
1598 IOUAmount{10'488'088'48170151, -8}));
-
1599 });
-
1600 }
-
1601
-
1602 void
-
1603 testInvalidWithdraw()
-
1604 {
-
1605 testcase("Invalid Withdraw");
-
1606
-
1607 using namespace jtx;
-
1608
-
1609 testAMM(
-
1610 [&](AMM& ammAlice, Env& env) {
-
1611 WithdrawArg args{
-
1612 .asset1Out = XRP(100),
-
1613 .err = ter(tecAMM_BALANCE),
-
1614 };
-
1615 ammAlice.withdraw(args);
-
1616 },
-
1617 {{XRP(99), USD(99)}});
-
1618
-
1619 testAMM(
-
1620 [&](AMM& ammAlice, Env& env) {
-
1621 WithdrawArg args{
-
1622 .asset1Out = USD(100),
-
1623 .err = ter(tecAMM_BALANCE),
-
1624 };
-
1625 ammAlice.withdraw(args);
-
1626 },
-
1627 {{XRP(99), USD(99)}});
-
1628
-
1629 {
-
1630 Env env{*this};
-
1631 env.fund(XRP(30'000), gw, alice, bob);
-
1632 env.close();
-
1633 env(fset(gw, asfRequireAuth));
-
1634 env.close();
-
1635 env(trust(alice, gw["USD"](30'000), 0));
-
1636 env(trust(gw, alice["USD"](0), tfSetfAuth));
-
1637 // Bob trusts Gateway to owe him USD...
-
1638 env(trust(bob, gw["USD"](30'000), 0));
-
1639 // ...but Gateway does not authorize Bob to hold its USD.
-
1640 env.close();
-
1641 env(pay(gw, alice, USD(10'000)));
-
1642 env.close();
-
1643 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
-
1644 WithdrawArg args{
-
1645 .account = bob,
-
1646 .asset1Out = USD(100),
-
1647 .err = ter(tecNO_AUTH),
-
1648 };
-
1649 ammAlice.withdraw(args);
-
1650 }
-
1651
-
1652 testAMM([&](AMM& ammAlice, Env& env) {
-
1653 // Invalid flags
-
1654 ammAlice.withdraw(
-
1655 alice,
-
1656 1'000'000,
-
1657 std::nullopt,
+
1592 std::nullopt,
+
1593 tfSingleAsset,
+
1594 std::nullopt,
+
1595 std::nullopt);
+
1596 BEAST_EXPECT(ammAlice.expectBalances(
+
1597 XRP(10'000),
+
1598 STAmount{USD, UINT64_C(10'999'99999999999), -11},
+
1599 IOUAmount{10'488'088'48170151, -8}));
+
1600 });
+
1601 }
+
1602
+
1603 void
+
1604 testInvalidWithdraw()
+
1605 {
+
1606 testcase("Invalid Withdraw");
+
1607
+
1608 using namespace jtx;
+
1609
+
1610 testAMM(
+
1611 [&](AMM& ammAlice, Env& env) {
+
1612 WithdrawArg args{
+
1613 .asset1Out = XRP(100),
+
1614 .err = ter(tecAMM_BALANCE),
+
1615 };
+
1616 ammAlice.withdraw(args);
+
1617 },
+
1618 {{XRP(99), USD(99)}});
+
1619
+
1620 testAMM(
+
1621 [&](AMM& ammAlice, Env& env) {
+
1622 WithdrawArg args{
+
1623 .asset1Out = USD(100),
+
1624 .err = ter(tecAMM_BALANCE),
+
1625 };
+
1626 ammAlice.withdraw(args);
+
1627 },
+
1628 {{XRP(99), USD(99)}});
+
1629
+
1630 {
+
1631 Env env{*this};
+
1632 env.fund(XRP(30'000), gw, alice, bob);
+
1633 env.close();
+
1634 env(fset(gw, asfRequireAuth));
+
1635 env.close();
+
1636 env(trust(alice, gw["USD"](30'000), 0));
+
1637 env(trust(gw, alice["USD"](0), tfSetfAuth));
+
1638 // Bob trusts Gateway to owe him USD...
+
1639 env(trust(bob, gw["USD"](30'000), 0));
+
1640 // ...but Gateway does not authorize Bob to hold its USD.
+
1641 env.close();
+
1642 env(pay(gw, alice, USD(10'000)));
+
1643 env.close();
+
1644 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
+
1645 WithdrawArg args{
+
1646 .account = bob,
+
1647 .asset1Out = USD(100),
+
1648 .err = ter(tecNO_AUTH),
+
1649 };
+
1650 ammAlice.withdraw(args);
+
1651 }
+
1652
+
1653 testAMM([&](AMM& ammAlice, Env& env) {
+
1654 // Invalid flags
+
1655 ammAlice.withdraw(
+
1656 alice,
+
1657 1'000'000,
1658 std::nullopt,
1659 std::nullopt,
-
1660 tfBurnable,
-
1661 std::nullopt,
+
1660 std::nullopt,
+
1661 tfBurnable,
1662 std::nullopt,
-
1663 ter(temINVALID_FLAG));
-
1664 ammAlice.withdraw(
-
1665 alice,
-
1666 1'000'000,
-
1667 std::nullopt,
+
1663 std::nullopt,
+
1664 ter(temINVALID_FLAG));
+
1665 ammAlice.withdraw(
+
1666 alice,
+
1667 1'000'000,
1668 std::nullopt,
1669 std::nullopt,
-
1670 tfTwoAssetIfEmpty,
-
1671 std::nullopt,
+
1670 std::nullopt,
+
1671 tfTwoAssetIfEmpty,
1672 std::nullopt,
-
1673 ter(temINVALID_FLAG));
-
1674
-
1675 // Invalid options
-
1676 std::vector<std::tuple<
-
1677 std::optional<std::uint32_t>,
-
1678 std::optional<STAmount>,
+
1673 std::nullopt,
+
1674 ter(temINVALID_FLAG));
+
1675
+
1676 // Invalid options
+
1677 std::vector<std::tuple<
+
1678 std::optional<std::uint32_t>,
1679 std::optional<STAmount>,
-
1680 std::optional<IOUAmount>,
-
1681 std::optional<std::uint32_t>,
-
1682 NotTEC>>
-
1683 invalidOptions = {
-
1684 // tokens, asset1Out, asset2Out, EPrice, flags, ter
-
1685 {std::nullopt,
-
1686 std::nullopt,
+
1680 std::optional<STAmount>,
+
1681 std::optional<IOUAmount>,
+
1682 std::optional<std::uint32_t>,
+
1683 NotTEC>>
+
1684 invalidOptions = {
+
1685 // tokens, asset1Out, asset2Out, EPrice, flags, ter
+
1686 {std::nullopt,
1687 std::nullopt,
1688 std::nullopt,
1689 std::nullopt,
-
1690 temMALFORMED},
-
1691 {std::nullopt,
-
1692 std::nullopt,
+
1690 std::nullopt,
+
1691 temMALFORMED},
+
1692 {std::nullopt,
1693 std::nullopt,
1694 std::nullopt,
-
1695 tfSingleAsset | tfTwoAsset,
-
1696 temMALFORMED},
-
1697 {1'000,
-
1698 std::nullopt,
+
1695 std::nullopt,
+
1696 tfSingleAsset | tfTwoAsset,
+
1697 temMALFORMED},
+
1698 {1'000,
1699 std::nullopt,
1700 std::nullopt,
-
1701 tfWithdrawAll,
-
1702 temMALFORMED},
-
1703 {std::nullopt,
-
1704 USD(0),
-
1705 XRP(100),
-
1706 std::nullopt,
-
1707 tfWithdrawAll | tfLPToken,
-
1708 temMALFORMED},
-
1709 {std::nullopt,
-
1710 std::nullopt,
-
1711 USD(100),
-
1712 std::nullopt,
-
1713 tfWithdrawAll,
-
1714 temMALFORMED},
-
1715 {std::nullopt,
-
1716 std::nullopt,
+
1701 std::nullopt,
+
1702 tfWithdrawAll,
+
1703 temMALFORMED},
+
1704 {std::nullopt,
+
1705 USD(0),
+
1706 XRP(100),
+
1707 std::nullopt,
+
1708 tfWithdrawAll | tfLPToken,
+
1709 temMALFORMED},
+
1710 {std::nullopt,
+
1711 std::nullopt,
+
1712 USD(100),
+
1713 std::nullopt,
+
1714 tfWithdrawAll,
+
1715 temMALFORMED},
+
1716 {std::nullopt,
1717 std::nullopt,
1718 std::nullopt,
-
1719 tfWithdrawAll | tfOneAssetWithdrawAll,
-
1720 temMALFORMED},
-
1721 {std::nullopt,
-
1722 USD(100),
-
1723 std::nullopt,
+
1719 std::nullopt,
+
1720 tfWithdrawAll | tfOneAssetWithdrawAll,
+
1721 temMALFORMED},
+
1722 {std::nullopt,
+
1723 USD(100),
1724 std::nullopt,
-
1725 tfWithdrawAll,
-
1726 temMALFORMED},
-
1727 {std::nullopt,
-
1728 std::nullopt,
+
1725 std::nullopt,
+
1726 tfWithdrawAll,
+
1727 temMALFORMED},
+
1728 {std::nullopt,
1729 std::nullopt,
1730 std::nullopt,
-
1731 tfOneAssetWithdrawAll,
-
1732 temMALFORMED},
-
1733 {1'000,
-
1734 std::nullopt,
-
1735 USD(100),
-
1736 std::nullopt,
+
1731 std::nullopt,
+
1732 tfOneAssetWithdrawAll,
+
1733 temMALFORMED},
+
1734 {1'000,
+
1735 std::nullopt,
+
1736 USD(100),
1737 std::nullopt,
-
1738 temMALFORMED},
-
1739 {std::nullopt,
-
1740 std::nullopt,
+
1738 std::nullopt,
+
1739 temMALFORMED},
+
1740 {std::nullopt,
1741 std::nullopt,
-
1742 IOUAmount{250, 0},
-
1743 tfWithdrawAll,
-
1744 temMALFORMED},
-
1745 {1'000,
-
1746 std::nullopt,
+
1742 std::nullopt,
+
1743 IOUAmount{250, 0},
+
1744 tfWithdrawAll,
+
1745 temMALFORMED},
+
1746 {1'000,
1747 std::nullopt,
-
1748 IOUAmount{250, 0},
-
1749 std::nullopt,
-
1750 temMALFORMED},
-
1751 {std::nullopt,
-
1752 std::nullopt,
-
1753 USD(100),
-
1754 IOUAmount{250, 0},
-
1755 std::nullopt,
-
1756 temMALFORMED},
-
1757 {std::nullopt,
-
1758 XRP(100),
-
1759 USD(100),
-
1760 IOUAmount{250, 0},
-
1761 std::nullopt,
-
1762 temMALFORMED},
-
1763 {1'000,
-
1764 XRP(100),
-
1765 USD(100),
-
1766 std::nullopt,
+
1748 std::nullopt,
+
1749 IOUAmount{250, 0},
+
1750 std::nullopt,
+
1751 temMALFORMED},
+
1752 {std::nullopt,
+
1753 std::nullopt,
+
1754 USD(100),
+
1755 IOUAmount{250, 0},
+
1756 std::nullopt,
+
1757 temMALFORMED},
+
1758 {std::nullopt,
+
1759 XRP(100),
+
1760 USD(100),
+
1761 IOUAmount{250, 0},
+
1762 std::nullopt,
+
1763 temMALFORMED},
+
1764 {1'000,
+
1765 XRP(100),
+
1766 USD(100),
1767 std::nullopt,
-
1768 temMALFORMED},
-
1769 {std::nullopt,
-
1770 XRP(100),
-
1771 USD(100),
-
1772 std::nullopt,
-
1773 tfWithdrawAll,
-
1774 temMALFORMED}};
-
1775 for (auto const& it : invalidOptions)
-
1776 {
-
1777 ammAlice.withdraw(
-
1778 alice,
-
1779 std::get<0>(it),
-
1780 std::get<1>(it),
-
1781 std::get<2>(it),
-
1782 std::get<3>(it),
-
1783 std::get<4>(it),
-
1784 std::nullopt,
+
1768 std::nullopt,
+
1769 temMALFORMED},
+
1770 {std::nullopt,
+
1771 XRP(100),
+
1772 USD(100),
+
1773 std::nullopt,
+
1774 tfWithdrawAll,
+
1775 temMALFORMED}};
+
1776 for (auto const& it : invalidOptions)
+
1777 {
+
1778 ammAlice.withdraw(
+
1779 alice,
+
1780 std::get<0>(it),
+
1781 std::get<1>(it),
+
1782 std::get<2>(it),
+
1783 std::get<3>(it),
+
1784 std::get<4>(it),
1785 std::nullopt,
-
1786 ter(std::get<5>(it)));
-
1787 }
-
1788
-
1789 // Invalid tokens
-
1790 ammAlice.withdraw(
-
1791 alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
-
1792 ammAlice.withdraw(
-
1793 alice,
-
1794 IOUAmount{-1},
-
1795 std::nullopt,
+
1786 std::nullopt,
+
1787 ter(std::get<5>(it)));
+
1788 }
+
1789
+
1790 // Invalid tokens
+
1791 ammAlice.withdraw(
+
1792 alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
+
1793 ammAlice.withdraw(
+
1794 alice,
+
1795 IOUAmount{-1},
1796 std::nullopt,
-
1797 ter(temBAD_AMM_TOKENS));
-
1798
-
1799 // Mismatched token, invalid Asset1Out issue
-
1800 ammAlice.withdraw(
-
1801 alice,
-
1802 GBP(100),
-
1803 std::nullopt,
+
1797 std::nullopt,
+
1798 ter(temBAD_AMM_TOKENS));
+
1799
+
1800 // Mismatched token, invalid Asset1Out issue
+
1801 ammAlice.withdraw(
+
1802 alice,
+
1803 GBP(100),
1804 std::nullopt,
-
1805 ter(temBAD_AMM_TOKENS));
-
1806
-
1807 // Mismatched token, invalid Asset2Out issue
-
1808 ammAlice.withdraw(
-
1809 alice,
-
1810 USD(100),
-
1811 GBP(100),
-
1812 std::nullopt,
-
1813 ter(temBAD_AMM_TOKENS));
-
1814
-
1815 // Mismatched token, Asset1Out.issue == Asset2Out.issue
-
1816 ammAlice.withdraw(
-
1817 alice,
-
1818 USD(100),
+
1805 std::nullopt,
+
1806 ter(temBAD_AMM_TOKENS));
+
1807
+
1808 // Mismatched token, invalid Asset2Out issue
+
1809 ammAlice.withdraw(
+
1810 alice,
+
1811 USD(100),
+
1812 GBP(100),
+
1813 std::nullopt,
+
1814 ter(temBAD_AMM_TOKENS));
+
1815
+
1816 // Mismatched token, Asset1Out.issue == Asset2Out.issue
+
1817 ammAlice.withdraw(
+
1818 alice,
1819 USD(100),
-
1820 std::nullopt,
-
1821 ter(temBAD_AMM_TOKENS));
-
1822
-
1823 // Invalid amount value
-
1824 ammAlice.withdraw(
-
1825 alice, USD(0), std::nullopt, std::nullopt, ter(temBAD_AMOUNT));
-
1826 ammAlice.withdraw(
-
1827 alice,
-
1828 USD(-100),
-
1829 std::nullopt,
+
1820 USD(100),
+
1821 std::nullopt,
+
1822 ter(temBAD_AMM_TOKENS));
+
1823
+
1824 // Invalid amount value
+
1825 ammAlice.withdraw(
+
1826 alice, USD(0), std::nullopt, std::nullopt, ter(temBAD_AMOUNT));
+
1827 ammAlice.withdraw(
+
1828 alice,
+
1829 USD(-100),
1830 std::nullopt,
-
1831 ter(temBAD_AMOUNT));
-
1832 ammAlice.withdraw(
-
1833 alice,
-
1834 USD(10),
-
1835 std::nullopt,
-
1836 IOUAmount{-1},
-
1837 ter(temBAD_AMOUNT));
-
1838
-
1839 // Invalid amount/token value, withdraw all tokens from one side
-
1840 // of the pool.
-
1841 ammAlice.withdraw(
-
1842 alice,
-
1843 USD(10'000),
-
1844 std::nullopt,
+
1831 std::nullopt,
+
1832 ter(temBAD_AMOUNT));
+
1833 ammAlice.withdraw(
+
1834 alice,
+
1835 USD(10),
+
1836 std::nullopt,
+
1837 IOUAmount{-1},
+
1838 ter(temBAD_AMOUNT));
+
1839
+
1840 // Invalid amount/token value, withdraw all tokens from one side
+
1841 // of the pool.
+
1842 ammAlice.withdraw(
+
1843 alice,
+
1844 USD(10'000),
1845 std::nullopt,
-
1846 ter(tecAMM_BALANCE));
-
1847 ammAlice.withdraw(
-
1848 alice,
-
1849 XRP(10'000),
-
1850 std::nullopt,
+
1846 std::nullopt,
+
1847 ter(tecAMM_BALANCE));
+
1848 ammAlice.withdraw(
+
1849 alice,
+
1850 XRP(10'000),
1851 std::nullopt,
-
1852 ter(tecAMM_BALANCE));
-
1853 ammAlice.withdraw(
-
1854 alice,
-
1855 std::nullopt,
-
1856 USD(0),
-
1857 std::nullopt,
+
1852 std::nullopt,
+
1853 ter(tecAMM_BALANCE));
+
1854 ammAlice.withdraw(
+
1855 alice,
+
1856 std::nullopt,
+
1857 USD(0),
1858 std::nullopt,
-
1859 tfOneAssetWithdrawAll,
-
1860 std::nullopt,
+
1859 std::nullopt,
+
1860 tfOneAssetWithdrawAll,
1861 std::nullopt,
-
1862 ter(tecAMM_BALANCE));
-
1863
-
1864 // Bad currency
-
1865 ammAlice.withdraw(
-
1866 alice,
-
1867 BAD(100),
-
1868 std::nullopt,
+
1862 std::nullopt,
+
1863 ter(tecAMM_BALANCE));
+
1864
+
1865 // Bad currency
+
1866 ammAlice.withdraw(
+
1867 alice,
+
1868 BAD(100),
1869 std::nullopt,
-
1870 ter(temBAD_CURRENCY));
-
1871
-
1872 // Invalid Account
-
1873 Account bad("bad");
-
1874 env.memoize(bad);
-
1875 ammAlice.withdraw(
-
1876 bad,
-
1877 1'000'000,
-
1878 std::nullopt,
+
1870 std::nullopt,
+
1871 ter(temBAD_CURRENCY));
+
1872
+
1873 // Invalid Account
+
1874 Account bad("bad");
+
1875 env.memoize(bad);
+
1876 ammAlice.withdraw(
+
1877 bad,
+
1878 1'000'000,
1879 std::nullopt,
1880 std::nullopt,
1881 std::nullopt,
1882 std::nullopt,
-
1883 seq(1),
-
1884 ter(terNO_ACCOUNT));
-
1885
-
1886 // Invalid AMM
-
1887 ammAlice.withdraw(
-
1888 alice,
-
1889 1'000,
-
1890 std::nullopt,
+
1883 std::nullopt,
+
1884 seq(1),
+
1885 ter(terNO_ACCOUNT));
+
1886
+
1887 // Invalid AMM
+
1888 ammAlice.withdraw(
+
1889 alice,
+
1890 1'000,
1891 std::nullopt,
1892 std::nullopt,
1893 std::nullopt,
-
1894 {{USD, GBP}},
-
1895 std::nullopt,
-
1896 ter(terNO_AMM));
-
1897
-
1898 // Carol is not a Liquidity Provider
-
1899 ammAlice.withdraw(
-
1900 carol, 10'000, std::nullopt, std::nullopt, ter(tecAMM_BALANCE));
-
1901
-
1902 // Withdraw entire one side of the pool.
-
1903 // Equal withdraw but due to XRP precision limit,
-
1904 // this results in full withdraw of XRP pool only,
-
1905 // while leaving a tiny amount in USD pool.
-
1906 ammAlice.withdraw(
-
1907 alice,
-
1908 IOUAmount{9'999'999'9999, -4},
-
1909 std::nullopt,
+
1894 std::nullopt,
+
1895 {{USD, GBP}},
+
1896 std::nullopt,
+
1897 ter(terNO_AMM));
+
1898
+
1899 // Carol is not a Liquidity Provider
+
1900 ammAlice.withdraw(
+
1901 carol, 10'000, std::nullopt, std::nullopt, ter(tecAMM_BALANCE));
+
1902
+
1903 // Withdraw entire one side of the pool.
+
1904 // Equal withdraw but due to XRP precision limit,
+
1905 // this results in full withdraw of XRP pool only,
+
1906 // while leaving a tiny amount in USD pool.
+
1907 ammAlice.withdraw(
+
1908 alice,
+
1909 IOUAmount{9'999'999'9999, -4},
1910 std::nullopt,
-
1911 ter(tecAMM_BALANCE));
-
1912 // Withdrawing from one side.
-
1913 // XRP by tokens
-
1914 ammAlice.withdraw(
-
1915 alice,
-
1916 IOUAmount(9'999'999'9999, -4),
-
1917 XRP(0),
-
1918 std::nullopt,
-
1919 ter(tecAMM_BALANCE));
-
1920 // USD by tokens
-
1921 ammAlice.withdraw(
-
1922 alice,
-
1923 IOUAmount(9'999'999'9, -1),
-
1924 USD(0),
-
1925 std::nullopt,
-
1926 ter(tecAMM_BALANCE));
-
1927 // XRP
-
1928 ammAlice.withdraw(
-
1929 alice,
-
1930 XRP(10'000),
-
1931 std::nullopt,
+
1911 std::nullopt,
+
1912 ter(tecAMM_BALANCE));
+
1913 // Withdrawing from one side.
+
1914 // XRP by tokens
+
1915 ammAlice.withdraw(
+
1916 alice,
+
1917 IOUAmount(9'999'999'9999, -4),
+
1918 XRP(0),
+
1919 std::nullopt,
+
1920 ter(tecAMM_BALANCE));
+
1921 // USD by tokens
+
1922 ammAlice.withdraw(
+
1923 alice,
+
1924 IOUAmount(9'999'999'9, -1),
+
1925 USD(0),
+
1926 std::nullopt,
+
1927 ter(tecAMM_BALANCE));
+
1928 // XRP
+
1929 ammAlice.withdraw(
+
1930 alice,
+
1931 XRP(10'000),
1932 std::nullopt,
-
1933 ter(tecAMM_BALANCE));
-
1934 // USD
-
1935 ammAlice.withdraw(
-
1936 alice,
-
1937 STAmount{USD, UINT64_C(9'999'9999999999999), -13},
-
1938 std::nullopt,
+
1933 std::nullopt,
+
1934 ter(tecAMM_BALANCE));
+
1935 // USD
+
1936 ammAlice.withdraw(
+
1937 alice,
+
1938 STAmount{USD, UINT64_C(9'999'9999999999999), -13},
1939 std::nullopt,
-
1940 ter(tecAMM_BALANCE));
-
1941 });
-
1942
-
1943 // Invalid AMM
-
1944 testAMM([&](AMM& ammAlice, Env& env) {
-
1945 ammAlice.withdrawAll(alice);
-
1946 ammAlice.withdraw(
-
1947 alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM));
-
1948 });
-
1949
-
1950 // Globally frozen asset
-
1951 testAMM([&](AMM& ammAlice, Env& env) {
-
1952 env(fset(gw, asfGlobalFreeze));
-
1953 env.close();
-
1954 // Can withdraw non-frozen token
-
1955 ammAlice.withdraw(alice, XRP(100));
-
1956 ammAlice.withdraw(
-
1957 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
-
1958 ammAlice.withdraw(
-
1959 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
-
1960 });
-
1961
-
1962 // Individually frozen (AMM) account
-
1963 testAMM([&](AMM& ammAlice, Env& env) {
-
1964 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
1965 env.close();
-
1966 // Can withdraw non-frozen token
-
1967 ammAlice.withdraw(alice, XRP(100));
-
1968 ammAlice.withdraw(
-
1969 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
-
1970 ammAlice.withdraw(
-
1971 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
-
1972 env(trust(gw, alice["USD"](0), tfClearFreeze));
-
1973 // Individually frozen AMM
-
1974 env(trust(
-
1975 gw,
-
1976 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
-
1977 tfSetFreeze));
-
1978 // Can withdraw non-frozen token
-
1979 ammAlice.withdraw(alice, XRP(100));
-
1980 ammAlice.withdraw(
-
1981 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
-
1982 ammAlice.withdraw(
-
1983 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
-
1984 });
-
1985
-
1986 // Carol withdraws more than she owns
-
1987 testAMM([&](AMM& ammAlice, Env&) {
-
1988 // Single deposit of 100000 worth of tokens,
-
1989 // which is 10% of the pool. Carol is LP now.
-
1990 ammAlice.deposit(carol, 1'000'000);
-
1991 BEAST_EXPECT(ammAlice.expectBalances(
-
1992 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
1993
-
1994 ammAlice.withdraw(
-
1995 carol,
-
1996 2'000'000,
-
1997 std::nullopt,
+
1940 std::nullopt,
+
1941 ter(tecAMM_BALANCE));
+
1942 });
+
1943
+
1944 // Invalid AMM
+
1945 testAMM([&](AMM& ammAlice, Env& env) {
+
1946 ammAlice.withdrawAll(alice);
+
1947 ammAlice.withdraw(
+
1948 alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM));
+
1949 });
+
1950
+
1951 // Globally frozen asset
+
1952 testAMM([&](AMM& ammAlice, Env& env) {
+
1953 env(fset(gw, asfGlobalFreeze));
+
1954 env.close();
+
1955 // Can withdraw non-frozen token
+
1956 ammAlice.withdraw(alice, XRP(100));
+
1957 ammAlice.withdraw(
+
1958 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
+
1959 ammAlice.withdraw(
+
1960 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
+
1961 });
+
1962
+
1963 // Individually frozen (AMM) account
+
1964 testAMM([&](AMM& ammAlice, Env& env) {
+
1965 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
1966 env.close();
+
1967 // Can withdraw non-frozen token
+
1968 ammAlice.withdraw(alice, XRP(100));
+
1969 ammAlice.withdraw(
+
1970 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
+
1971 ammAlice.withdraw(
+
1972 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
+
1973 env(trust(gw, alice["USD"](0), tfClearFreeze));
+
1974 // Individually frozen AMM
+
1975 env(trust(
+
1976 gw,
+
1977 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
+
1978 tfSetFreeze));
+
1979 // Can withdraw non-frozen token
+
1980 ammAlice.withdraw(alice, XRP(100));
+
1981 ammAlice.withdraw(
+
1982 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
+
1983 ammAlice.withdraw(
+
1984 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
+
1985 });
+
1986
+
1987 // Carol withdraws more than she owns
+
1988 testAMM([&](AMM& ammAlice, Env&) {
+
1989 // Single deposit of 100000 worth of tokens,
+
1990 // which is 10% of the pool. Carol is LP now.
+
1991 ammAlice.deposit(carol, 1'000'000);
+
1992 BEAST_EXPECT(ammAlice.expectBalances(
+
1993 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
1994
+
1995 ammAlice.withdraw(
+
1996 carol,
+
1997 2'000'000,
1998 std::nullopt,
-
1999 ter(tecAMM_INVALID_TOKENS));
-
2000 BEAST_EXPECT(ammAlice.expectBalances(
-
2001 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
2002 });
-
2003
-
2004 // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
-
2005 // to withdraw are 0.
-
2006 testAMM([&](AMM& ammAlice, Env&) {
-
2007 ammAlice.deposit(carol, 1'000'000);
-
2008 ammAlice.withdraw(
-
2009 carol,
-
2010 USD(100),
-
2011 std::nullopt,
-
2012 IOUAmount{500, 0},
-
2013 ter(tecAMM_FAILED));
-
2014 });
-
2015
-
2016 // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
-
2017 // to withdraw are greater than the LP shares.
-
2018 testAMM([&](AMM& ammAlice, Env&) {
-
2019 ammAlice.deposit(carol, 1'000'000);
-
2020 ammAlice.withdraw(
-
2021 carol,
-
2022 USD(100),
-
2023 std::nullopt,
-
2024 IOUAmount{600, 0},
-
2025 ter(tecAMM_INVALID_TOKENS));
-
2026 });
-
2027
-
2028 // Withdraw with EPrice limit. Fails to withdraw, amount1
-
2029 // to withdraw is less than 1700USD.
-
2030 testAMM([&](AMM& ammAlice, Env&) {
-
2031 ammAlice.deposit(carol, 1'000'000);
-
2032 ammAlice.withdraw(
-
2033 carol,
-
2034 USD(1'700),
-
2035 std::nullopt,
-
2036 IOUAmount{520, 0},
-
2037 ter(tecAMM_FAILED));
-
2038 });
-
2039
-
2040 // Deposit/Withdraw the same amount with the trading fee
-
2041 testAMM(
-
2042 [&](AMM& ammAlice, Env&) {
-
2043 ammAlice.deposit(carol, USD(1'000));
-
2044 ammAlice.withdraw(
-
2045 carol,
-
2046 USD(1'000),
-
2047 std::nullopt,
+
1999 std::nullopt,
+
2000 ter(tecAMM_INVALID_TOKENS));
+
2001 BEAST_EXPECT(ammAlice.expectBalances(
+
2002 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
2003 });
+
2004
+
2005 // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
+
2006 // to withdraw are 0.
+
2007 testAMM([&](AMM& ammAlice, Env&) {
+
2008 ammAlice.deposit(carol, 1'000'000);
+
2009 ammAlice.withdraw(
+
2010 carol,
+
2011 USD(100),
+
2012 std::nullopt,
+
2013 IOUAmount{500, 0},
+
2014 ter(tecAMM_FAILED));
+
2015 });
+
2016
+
2017 // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
+
2018 // to withdraw are greater than the LP shares.
+
2019 testAMM([&](AMM& ammAlice, Env&) {
+
2020 ammAlice.deposit(carol, 1'000'000);
+
2021 ammAlice.withdraw(
+
2022 carol,
+
2023 USD(100),
+
2024 std::nullopt,
+
2025 IOUAmount{600, 0},
+
2026 ter(tecAMM_INVALID_TOKENS));
+
2027 });
+
2028
+
2029 // Withdraw with EPrice limit. Fails to withdraw, amount1
+
2030 // to withdraw is less than 1700USD.
+
2031 testAMM([&](AMM& ammAlice, Env&) {
+
2032 ammAlice.deposit(carol, 1'000'000);
+
2033 ammAlice.withdraw(
+
2034 carol,
+
2035 USD(1'700),
+
2036 std::nullopt,
+
2037 IOUAmount{520, 0},
+
2038 ter(tecAMM_FAILED));
+
2039 });
+
2040
+
2041 // Deposit/Withdraw the same amount with the trading fee
+
2042 testAMM(
+
2043 [&](AMM& ammAlice, Env&) {
+
2044 ammAlice.deposit(carol, USD(1'000));
+
2045 ammAlice.withdraw(
+
2046 carol,
+
2047 USD(1'000),
2048 std::nullopt,
-
2049 ter(tecAMM_INVALID_TOKENS));
-
2050 },
-
2051 std::nullopt,
-
2052 1'000);
-
2053 testAMM(
-
2054 [&](AMM& ammAlice, Env&) {
-
2055 ammAlice.deposit(carol, XRP(1'000));
-
2056 ammAlice.withdraw(
-
2057 carol,
-
2058 XRP(1'000),
-
2059 std::nullopt,
+
2049 std::nullopt,
+
2050 ter(tecAMM_INVALID_TOKENS));
+
2051 },
+
2052 std::nullopt,
+
2053 1'000);
+
2054 testAMM(
+
2055 [&](AMM& ammAlice, Env&) {
+
2056 ammAlice.deposit(carol, XRP(1'000));
+
2057 ammAlice.withdraw(
+
2058 carol,
+
2059 XRP(1'000),
2060 std::nullopt,
-
2061 ter(tecAMM_INVALID_TOKENS));
-
2062 },
-
2063 std::nullopt,
-
2064 1'000);
-
2065
-
2066 // Deposit/Withdraw the same amount fails due to the tokens adjustment
-
2067 testAMM([&](AMM& ammAlice, Env&) {
-
2068 ammAlice.deposit(carol, STAmount{USD, 1, -6});
-
2069 ammAlice.withdraw(
-
2070 carol,
-
2071 STAmount{USD, 1, -6},
-
2072 std::nullopt,
+
2061 std::nullopt,
+
2062 ter(tecAMM_INVALID_TOKENS));
+
2063 },
+
2064 std::nullopt,
+
2065 1'000);
+
2066
+
2067 // Deposit/Withdraw the same amount fails due to the tokens adjustment
+
2068 testAMM([&](AMM& ammAlice, Env&) {
+
2069 ammAlice.deposit(carol, STAmount{USD, 1, -6});
+
2070 ammAlice.withdraw(
+
2071 carol,
+
2072 STAmount{USD, 1, -6},
2073 std::nullopt,
-
2074 ter(tecAMM_INVALID_TOKENS));
-
2075 });
-
2076
-
2077 // Withdraw close to one side of the pool. Account's LP tokens
-
2078 // are rounded to all LP tokens.
-
2079 testAMM([&](AMM& ammAlice, Env&) {
-
2080 ammAlice.withdraw(
-
2081 alice,
-
2082 STAmount{USD, UINT64_C(9'999'999999999999), -12},
-
2083 std::nullopt,
+
2074 std::nullopt,
+
2075 ter(tecAMM_INVALID_TOKENS));
+
2076 });
+
2077
+
2078 // Withdraw close to one side of the pool. Account's LP tokens
+
2079 // are rounded to all LP tokens.
+
2080 testAMM([&](AMM& ammAlice, Env&) {
+
2081 ammAlice.withdraw(
+
2082 alice,
+
2083 STAmount{USD, UINT64_C(9'999'999999999999), -12},
2084 std::nullopt,
-
2085 ter(tecAMM_BALANCE));
-
2086 });
-
2087
-
2088 // Tiny withdraw
-
2089 testAMM([&](AMM& ammAlice, Env&) {
-
2090 // XRP amount to withdraw is 0
-
2091 ammAlice.withdraw(
-
2092 alice,
-
2093 IOUAmount{1, -5},
-
2094 std::nullopt,
+
2085 std::nullopt,
+
2086 ter(tecAMM_BALANCE));
+
2087 });
+
2088
+
2089 // Tiny withdraw
+
2090 testAMM([&](AMM& ammAlice, Env&) {
+
2091 // XRP amount to withdraw is 0
+
2092 ammAlice.withdraw(
+
2093 alice,
+
2094 IOUAmount{1, -5},
2095 std::nullopt,
-
2096 ter(tecAMM_FAILED));
-
2097 // Calculated tokens to withdraw are 0
-
2098 ammAlice.withdraw(
-
2099 alice,
-
2100 std::nullopt,
-
2101 STAmount{USD, 1, -11},
-
2102 std::nullopt,
-
2103 ter(tecAMM_INVALID_TOKENS));
-
2104 ammAlice.deposit(carol, STAmount{USD, 1, -10});
-
2105 ammAlice.withdraw(
-
2106 carol,
-
2107 std::nullopt,
-
2108 STAmount{USD, 1, -9},
-
2109 std::nullopt,
-
2110 ter(tecAMM_INVALID_TOKENS));
-
2111 ammAlice.withdraw(
-
2112 carol,
-
2113 std::nullopt,
-
2114 XRPAmount{1},
-
2115 std::nullopt,
-
2116 ter(tecAMM_INVALID_TOKENS));
-
2117 });
-
2118 }
-
2119
-
2120 void
-
2121 testWithdraw()
-
2122 {
-
2123 testcase("Withdraw");
-
2124
-
2125 using namespace jtx;
-
2126
-
2127 // Equal withdrawal by Carol: 1000000 of tokens, 10% of the current
-
2128 // pool
-
2129 testAMM([&](AMM& ammAlice, Env& env) {
-
2130 auto const baseFee = env.current()->fees().base.drops();
-
2131 // Single deposit of 100000 worth of tokens,
-
2132 // which is 10% of the pool. Carol is LP now.
-
2133 ammAlice.deposit(carol, 1'000'000);
-
2134 BEAST_EXPECT(ammAlice.expectBalances(
-
2135 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
2136 BEAST_EXPECT(
-
2137 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
-
2138 // 30,000 less deposited 1,000
-
2139 BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
-
2140 // 30,000 less deposited 1,000 and 10 drops tx fee
-
2141 BEAST_EXPECT(expectLedgerEntryRoot(
-
2142 env, carol, XRPAmount{29'000'000'000 - baseFee}));
-
2143
-
2144 // Carol withdraws all tokens
-
2145 ammAlice.withdraw(carol, 1'000'000);
-
2146 BEAST_EXPECT(
-
2147 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
-
2148 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
2149 BEAST_EXPECT(expectLedgerEntryRoot(
-
2150 env, carol, XRPAmount{30'000'000'000 - 2 * baseFee}));
-
2151 });
-
2152
-
2153 // Equal withdrawal by tokens 1000000, 10%
-
2154 // of the current pool
-
2155 testAMM([&](AMM& ammAlice, Env&) {
-
2156 ammAlice.withdraw(alice, 1'000'000);
-
2157 BEAST_EXPECT(ammAlice.expectBalances(
-
2158 XRP(9'000), USD(9'000), IOUAmount{9'000'000, 0}));
-
2159 });
-
2160
-
2161 // Equal withdrawal with a limit. Withdraw XRP200.
-
2162 // If proportional withdraw of USD is less than 100
-
2163 // then withdraw that amount, otherwise withdraw USD100
-
2164 // and proportionally withdraw XRP. It's the latter
-
2165 // in this case - XRP100/USD100.
-
2166 testAMM([&](AMM& ammAlice, Env&) {
-
2167 ammAlice.withdraw(alice, XRP(200), USD(100));
-
2168 BEAST_EXPECT(ammAlice.expectBalances(
-
2169 XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
-
2170 });
-
2171
-
2172 // Equal withdrawal with a limit. XRP100/USD100.
-
2173 testAMM([&](AMM& ammAlice, Env&) {
-
2174 ammAlice.withdraw(alice, XRP(100), USD(200));
-
2175 BEAST_EXPECT(ammAlice.expectBalances(
-
2176 XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
-
2177 });
-
2178
-
2179 // Single withdrawal by amount XRP1000
-
2180 testAMM([&](AMM& ammAlice, Env&) {
-
2181 ammAlice.withdraw(alice, XRP(1'000));
-
2182 BEAST_EXPECT(ammAlice.expectBalances(
-
2183 XRP(9'000), USD(10'000), IOUAmount{9'486'832'98050514, -8}));
-
2184 });
-
2185
-
2186 // Single withdrawal by tokens 10000.
-
2187 testAMM([&](AMM& ammAlice, Env&) {
-
2188 ammAlice.withdraw(alice, 10'000, USD(0));
-
2189 BEAST_EXPECT(ammAlice.expectBalances(
-
2190 XRP(10'000), USD(9980.01), IOUAmount{9'990'000, 0}));
-
2191 });
-
2192
-
2193 // Withdraw all tokens.
-
2194 testAMM([&](AMM& ammAlice, Env& env) {
-
2195 env(trust(carol, STAmount{ammAlice.lptIssue(), 10'000}));
-
2196 // Can SetTrust only for AMM LP tokens
-
2197 env(trust(
-
2198 carol,
-
2199 STAmount{
-
2200 Issue{EUR.currency, ammAlice.ammAccount()}, 10'000}),
-
2201 ter(tecNO_PERMISSION));
-
2202 env.close();
-
2203 ammAlice.withdrawAll(alice);
-
2204 BEAST_EXPECT(!ammAlice.ammExists());
-
2205
-
2206 BEAST_EXPECT(!env.le(keylet::ownerDir(ammAlice.ammAccount())));
-
2207
-
2208 // Can create AMM for the XRP/USD pair
-
2209 AMM ammCarol(env, carol, XRP(10'000), USD(10'000));
-
2210 BEAST_EXPECT(ammCarol.expectBalances(
-
2211 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
-
2212 });
-
2213
-
2214 // Single deposit 1000USD, withdraw all tokens in USD
-
2215 testAMM([&](AMM& ammAlice, Env& env) {
-
2216 ammAlice.deposit(carol, USD(1'000));
-
2217 ammAlice.withdrawAll(carol, USD(0));
-
2218 BEAST_EXPECT(ammAlice.expectBalances(
-
2219 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
-
2220 BEAST_EXPECT(
-
2221 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
-
2222 });
-
2223
-
2224 // Single deposit 1000USD, withdraw all tokens in XRP
-
2225 testAMM([&](AMM& ammAlice, Env&) {
-
2226 ammAlice.deposit(carol, USD(1'000));
-
2227 ammAlice.withdrawAll(carol, XRP(0));
-
2228 BEAST_EXPECT(ammAlice.expectBalances(
-
2229 XRPAmount(9'090'909'091),
-
2230 STAmount{USD, UINT64_C(10'999'99999999999), -11},
-
2231 IOUAmount{10'000'000, 0}));
-
2232 });
-
2233
-
2234 // Single deposit/withdraw by the same account
-
2235 testAMM([&](AMM& ammAlice, Env&) {
-
2236 // Since a smaller amount might be deposited due to
-
2237 // the lp tokens adjustment, withdrawing by tokens
-
2238 // is generally preferred to withdrawing by amount.
-
2239 auto lpTokens = ammAlice.deposit(carol, USD(1'000));
-
2240 ammAlice.withdraw(carol, lpTokens, USD(0));
-
2241 lpTokens = ammAlice.deposit(carol, STAmount(USD, 1, -6));
-
2242 ammAlice.withdraw(carol, lpTokens, USD(0));
-
2243 lpTokens = ammAlice.deposit(carol, XRPAmount(1));
-
2244 ammAlice.withdraw(carol, lpTokens, XRPAmount(0));
-
2245 BEAST_EXPECT(ammAlice.expectBalances(
-
2246 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
2247 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
2248 });
-
2249
-
2250 // Single deposit by different accounts and then withdraw
-
2251 // in reverse.
-
2252 testAMM([&](AMM& ammAlice, Env&) {
-
2253 auto const carolTokens = ammAlice.deposit(carol, USD(1'000));
-
2254 auto const aliceTokens = ammAlice.deposit(alice, USD(1'000));
-
2255 ammAlice.withdraw(alice, aliceTokens, USD(0));
-
2256 ammAlice.withdraw(carol, carolTokens, USD(0));
-
2257 BEAST_EXPECT(ammAlice.expectBalances(
-
2258 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
2259 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
2260 BEAST_EXPECT(ammAlice.expectLPTokens(alice, ammAlice.tokens()));
-
2261 });
-
2262
-
2263 // Equal deposit 10%, withdraw all tokens
-
2264 testAMM([&](AMM& ammAlice, Env&) {
-
2265 ammAlice.deposit(carol, 1'000'000);
-
2266 ammAlice.withdrawAll(carol);
-
2267 BEAST_EXPECT(ammAlice.expectBalances(
-
2268 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
-
2269 });
-
2270
-
2271 // Equal deposit 10%, withdraw all tokens in USD
-
2272 testAMM([&](AMM& ammAlice, Env&) {
-
2273 ammAlice.deposit(carol, 1'000'000);
-
2274 ammAlice.withdrawAll(carol, USD(0));
-
2275 BEAST_EXPECT(ammAlice.expectBalances(
-
2276 XRP(11'000),
-
2277 STAmount{USD, UINT64_C(9'090'909090909092), -12},
-
2278 IOUAmount{10'000'000, 0}));
-
2279 });
-
2280
-
2281 // Equal deposit 10%, withdraw all tokens in XRP
-
2282 testAMM([&](AMM& ammAlice, Env&) {
-
2283 ammAlice.deposit(carol, 1'000'000);
-
2284 ammAlice.withdrawAll(carol, XRP(0));
-
2285 BEAST_EXPECT(ammAlice.expectBalances(
-
2286 XRPAmount(9'090'909'091),
-
2287 USD(11'000),
-
2288 IOUAmount{10'000'000, 0}));
-
2289 });
-
2290
-
2291 auto const all = supported_amendments();
-
2292 // Withdraw with EPrice limit.
-
2293 testAMM(
-
2294 [&](AMM& ammAlice, Env& env) {
-
2295 ammAlice.deposit(carol, 1'000'000);
-
2296 ammAlice.withdraw(
-
2297 carol, USD(100), std::nullopt, IOUAmount{520, 0});
-
2298 if (!env.current()->rules().enabled(fixAMMv1_1))
-
2299 BEAST_EXPECT(
-
2300 ammAlice.expectBalances(
-
2301 XRPAmount(11'000'000'000),
-
2302 STAmount{USD, UINT64_C(9'372'781065088757), -12},
-
2303 IOUAmount{10'153'846'15384616, -8}) &&
-
2304 ammAlice.expectLPTokens(
-
2305 carol, IOUAmount{153'846'15384616, -8}));
-
2306 else
-
2307 BEAST_EXPECT(
-
2308 ammAlice.expectBalances(
-
2309 XRPAmount(11'000'000'000),
-
2310 STAmount{USD, UINT64_C(9'372'781065088769), -12},
-
2311 IOUAmount{10'153'846'15384616, -8}) &&
-
2312 ammAlice.expectLPTokens(
-
2313 carol, IOUAmount{153'846'15384616, -8}));
-
2314 ammAlice.withdrawAll(carol);
-
2315 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
2316 },
-
2317 std::nullopt,
-
2318 0,
-
2319 std::nullopt,
-
2320 {all, all - fixAMMv1_1});
-
2321
-
2322 // Withdraw with EPrice limit. AssetOut is 0.
-
2323 testAMM(
-
2324 [&](AMM& ammAlice, Env& env) {
-
2325 ammAlice.deposit(carol, 1'000'000);
-
2326 ammAlice.withdraw(
-
2327 carol, USD(0), std::nullopt, IOUAmount{520, 0});
-
2328 if (!env.current()->rules().enabled(fixAMMv1_1))
-
2329 BEAST_EXPECT(
-
2330 ammAlice.expectBalances(
-
2331 XRPAmount(11'000'000'000),
-
2332 STAmount{USD, UINT64_C(9'372'781065088757), -12},
-
2333 IOUAmount{10'153'846'15384616, -8}) &&
-
2334 ammAlice.expectLPTokens(
-
2335 carol, IOUAmount{153'846'15384616, -8}));
-
2336 else
-
2337 BEAST_EXPECT(
-
2338 ammAlice.expectBalances(
-
2339 XRPAmount(11'000'000'000),
-
2340 STAmount{USD, UINT64_C(9'372'781065088769), -12},
-
2341 IOUAmount{10'153'846'15384616, -8}) &&
-
2342 ammAlice.expectLPTokens(
-
2343 carol, IOUAmount{153'846'15384616, -8}));
-
2344 },
-
2345 std::nullopt,
-
2346 0,
-
2347 std::nullopt,
-
2348 {all, all - fixAMMv1_1});
-
2349
-
2350 // IOU to IOU + transfer fee
-
2351 {
-
2352 Env env{*this};
-
2353 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
-
2354 env(rate(gw, 1.25));
-
2355 env.close();
-
2356 // no transfer fee on create
-
2357 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
-
2358 BEAST_EXPECT(ammAlice.expectBalances(
-
2359 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
-
2360 BEAST_EXPECT(expectLine(env, alice, USD(0)));
-
2361 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
-
2362 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
-
2363 // no transfer fee on deposit
-
2364 ammAlice.deposit(carol, 10);
-
2365 BEAST_EXPECT(ammAlice.expectBalances(
-
2366 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
-
2367 BEAST_EXPECT(expectLine(env, carol, USD(0)));
-
2368 BEAST_EXPECT(expectLine(env, carol, BTC(0)));
-
2369 // no transfer fee on withdraw
-
2370 ammAlice.withdraw(carol, 10);
-
2371 BEAST_EXPECT(ammAlice.expectBalances(
-
2372 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
-
2373 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
-
2374 BEAST_EXPECT(expectLine(env, carol, USD(2'000)));
-
2375 BEAST_EXPECT(expectLine(env, carol, BTC(0.05)));
-
2376 }
-
2377
-
2378 // Tiny withdraw
-
2379 testAMM([&](AMM& ammAlice, Env&) {
-
2380 // By tokens
-
2381 ammAlice.withdraw(alice, IOUAmount{1, -3});
-
2382 BEAST_EXPECT(ammAlice.expectBalances(
-
2383 XRPAmount{9'999'999'999},
-
2384 STAmount{USD, UINT64_C(9'999'999999), -6},
-
2385 IOUAmount{9'999'999'999, -3}));
-
2386 });
-
2387 testAMM([&](AMM& ammAlice, Env&) {
-
2388 // Single XRP pool
-
2389 ammAlice.withdraw(alice, std::nullopt, XRPAmount{1});
-
2390 BEAST_EXPECT(ammAlice.expectBalances(
-
2391 XRPAmount{9'999'999'999},
-
2392 USD(10'000),
-
2393 IOUAmount{9'999'999'9995, -4}));
-
2394 });
-
2395 testAMM([&](AMM& ammAlice, Env&) {
-
2396 // Single USD pool
-
2397 ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
-
2398 BEAST_EXPECT(ammAlice.expectBalances(
-
2399 XRP(10'000),
-
2400 STAmount{USD, UINT64_C(9'999'9999999999), -10},
-
2401 IOUAmount{9'999'999'99999995, -8}));
-
2402 });
-
2403
-
2404 // Withdraw close to entire pool
-
2405 // Equal by tokens
-
2406 testAMM([&](AMM& ammAlice, Env&) {
-
2407 ammAlice.withdraw(alice, IOUAmount{9'999'999'999, -3});
-
2408 BEAST_EXPECT(ammAlice.expectBalances(
-
2409 XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
-
2410 });
-
2411 // USD by tokens
-
2412 testAMM([&](AMM& ammAlice, Env&) {
-
2413 ammAlice.withdraw(alice, IOUAmount{9'999'999}, USD(0));
-
2414 BEAST_EXPECT(ammAlice.expectBalances(
-
2415 XRP(10'000), STAmount{USD, 1, -10}, IOUAmount{1}));
-
2416 });
-
2417 // XRP by tokens
-
2418 testAMM([&](AMM& ammAlice, Env&) {
-
2419 ammAlice.withdraw(alice, IOUAmount{9'999'900}, XRP(0));
-
2420 BEAST_EXPECT(ammAlice.expectBalances(
-
2421 XRPAmount{1}, USD(10'000), IOUAmount{100}));
-
2422 });
-
2423 // USD
-
2424 testAMM([&](AMM& ammAlice, Env&) {
-
2425 ammAlice.withdraw(
-
2426 alice, STAmount{USD, UINT64_C(9'999'99999999999), -11});
-
2427 BEAST_EXPECT(ammAlice.expectBalances(
-
2428 XRP(10000), STAmount{USD, 1, -11}, IOUAmount{316227765, -9}));
-
2429 });
-
2430 // XRP
-
2431 testAMM([&](AMM& ammAlice, Env&) {
-
2432 ammAlice.withdraw(alice, XRPAmount{9'999'999'999});
-
2433 BEAST_EXPECT(ammAlice.expectBalances(
-
2434 XRPAmount{1}, USD(10'000), IOUAmount{100}));
-
2435 });
-
2436 }
-
2437
-
2438 void
-
2439 testInvalidFeeVote()
-
2440 {
-
2441 testcase("Invalid Fee Vote");
-
2442 using namespace jtx;
-
2443
-
2444 testAMM([&](AMM& ammAlice, Env& env) {
-
2445 // Invalid flags
-
2446 ammAlice.vote(
-
2447 std::nullopt,
-
2448 1'000,
-
2449 tfWithdrawAll,
-
2450 std::nullopt,
+
2096 std::nullopt,
+
2097 ter(tecAMM_FAILED));
+
2098 // Calculated tokens to withdraw are 0
+
2099 ammAlice.withdraw(
+
2100 alice,
+
2101 std::nullopt,
+
2102 STAmount{USD, 1, -11},
+
2103 std::nullopt,
+
2104 ter(tecAMM_INVALID_TOKENS));
+
2105 ammAlice.deposit(carol, STAmount{USD, 1, -10});
+
2106 ammAlice.withdraw(
+
2107 carol,
+
2108 std::nullopt,
+
2109 STAmount{USD, 1, -9},
+
2110 std::nullopt,
+
2111 ter(tecAMM_INVALID_TOKENS));
+
2112 ammAlice.withdraw(
+
2113 carol,
+
2114 std::nullopt,
+
2115 XRPAmount{1},
+
2116 std::nullopt,
+
2117 ter(tecAMM_INVALID_TOKENS));
+
2118 });
+
2119 }
+
2120
+
2121 void
+
2122 testWithdraw()
+
2123 {
+
2124 testcase("Withdraw");
+
2125
+
2126 using namespace jtx;
+
2127
+
2128 // Equal withdrawal by Carol: 1000000 of tokens, 10% of the current
+
2129 // pool
+
2130 testAMM([&](AMM& ammAlice, Env& env) {
+
2131 auto const baseFee = env.current()->fees().base.drops();
+
2132 // Single deposit of 100000 worth of tokens,
+
2133 // which is 10% of the pool. Carol is LP now.
+
2134 ammAlice.deposit(carol, 1'000'000);
+
2135 BEAST_EXPECT(ammAlice.expectBalances(
+
2136 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
2137 BEAST_EXPECT(
+
2138 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
+
2139 // 30,000 less deposited 1,000
+
2140 BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
+
2141 // 30,000 less deposited 1,000 and 10 drops tx fee
+
2142 BEAST_EXPECT(expectLedgerEntryRoot(
+
2143 env, carol, XRPAmount{29'000'000'000 - baseFee}));
+
2144
+
2145 // Carol withdraws all tokens
+
2146 ammAlice.withdraw(carol, 1'000'000);
+
2147 BEAST_EXPECT(
+
2148 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
+
2149 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
2150 BEAST_EXPECT(expectLedgerEntryRoot(
+
2151 env, carol, XRPAmount{30'000'000'000 - 2 * baseFee}));
+
2152 });
+
2153
+
2154 // Equal withdrawal by tokens 1000000, 10%
+
2155 // of the current pool
+
2156 testAMM([&](AMM& ammAlice, Env&) {
+
2157 ammAlice.withdraw(alice, 1'000'000);
+
2158 BEAST_EXPECT(ammAlice.expectBalances(
+
2159 XRP(9'000), USD(9'000), IOUAmount{9'000'000, 0}));
+
2160 });
+
2161
+
2162 // Equal withdrawal with a limit. Withdraw XRP200.
+
2163 // If proportional withdraw of USD is less than 100
+
2164 // then withdraw that amount, otherwise withdraw USD100
+
2165 // and proportionally withdraw XRP. It's the latter
+
2166 // in this case - XRP100/USD100.
+
2167 testAMM([&](AMM& ammAlice, Env&) {
+
2168 ammAlice.withdraw(alice, XRP(200), USD(100));
+
2169 BEAST_EXPECT(ammAlice.expectBalances(
+
2170 XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
+
2171 });
+
2172
+
2173 // Equal withdrawal with a limit. XRP100/USD100.
+
2174 testAMM([&](AMM& ammAlice, Env&) {
+
2175 ammAlice.withdraw(alice, XRP(100), USD(200));
+
2176 BEAST_EXPECT(ammAlice.expectBalances(
+
2177 XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
+
2178 });
+
2179
+
2180 // Single withdrawal by amount XRP1000
+
2181 testAMM([&](AMM& ammAlice, Env&) {
+
2182 ammAlice.withdraw(alice, XRP(1'000));
+
2183 BEAST_EXPECT(ammAlice.expectBalances(
+
2184 XRP(9'000), USD(10'000), IOUAmount{9'486'832'98050514, -8}));
+
2185 });
+
2186
+
2187 // Single withdrawal by tokens 10000.
+
2188 testAMM([&](AMM& ammAlice, Env&) {
+
2189 ammAlice.withdraw(alice, 10'000, USD(0));
+
2190 BEAST_EXPECT(ammAlice.expectBalances(
+
2191 XRP(10'000), USD(9980.01), IOUAmount{9'990'000, 0}));
+
2192 });
+
2193
+
2194 // Withdraw all tokens.
+
2195 testAMM([&](AMM& ammAlice, Env& env) {
+
2196 env(trust(carol, STAmount{ammAlice.lptIssue(), 10'000}));
+
2197 // Can SetTrust only for AMM LP tokens
+
2198 env(trust(
+
2199 carol,
+
2200 STAmount{
+
2201 Issue{EUR.currency, ammAlice.ammAccount()}, 10'000}),
+
2202 ter(tecNO_PERMISSION));
+
2203 env.close();
+
2204 ammAlice.withdrawAll(alice);
+
2205 BEAST_EXPECT(!ammAlice.ammExists());
+
2206
+
2207 BEAST_EXPECT(!env.le(keylet::ownerDir(ammAlice.ammAccount())));
+
2208
+
2209 // Can create AMM for the XRP/USD pair
+
2210 AMM ammCarol(env, carol, XRP(10'000), USD(10'000));
+
2211 BEAST_EXPECT(ammCarol.expectBalances(
+
2212 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
+
2213 });
+
2214
+
2215 // Single deposit 1000USD, withdraw all tokens in USD
+
2216 testAMM([&](AMM& ammAlice, Env& env) {
+
2217 ammAlice.deposit(carol, USD(1'000));
+
2218 ammAlice.withdrawAll(carol, USD(0));
+
2219 BEAST_EXPECT(ammAlice.expectBalances(
+
2220 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
+
2221 BEAST_EXPECT(
+
2222 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
+
2223 });
+
2224
+
2225 // Single deposit 1000USD, withdraw all tokens in XRP
+
2226 testAMM([&](AMM& ammAlice, Env&) {
+
2227 ammAlice.deposit(carol, USD(1'000));
+
2228 ammAlice.withdrawAll(carol, XRP(0));
+
2229 BEAST_EXPECT(ammAlice.expectBalances(
+
2230 XRPAmount(9'090'909'091),
+
2231 STAmount{USD, UINT64_C(10'999'99999999999), -11},
+
2232 IOUAmount{10'000'000, 0}));
+
2233 });
+
2234
+
2235 // Single deposit/withdraw by the same account
+
2236 testAMM([&](AMM& ammAlice, Env&) {
+
2237 // Since a smaller amount might be deposited due to
+
2238 // the lp tokens adjustment, withdrawing by tokens
+
2239 // is generally preferred to withdrawing by amount.
+
2240 auto lpTokens = ammAlice.deposit(carol, USD(1'000));
+
2241 ammAlice.withdraw(carol, lpTokens, USD(0));
+
2242 lpTokens = ammAlice.deposit(carol, STAmount(USD, 1, -6));
+
2243 ammAlice.withdraw(carol, lpTokens, USD(0));
+
2244 lpTokens = ammAlice.deposit(carol, XRPAmount(1));
+
2245 ammAlice.withdraw(carol, lpTokens, XRPAmount(0));
+
2246 BEAST_EXPECT(ammAlice.expectBalances(
+
2247 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
2248 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
2249 });
+
2250
+
2251 // Single deposit by different accounts and then withdraw
+
2252 // in reverse.
+
2253 testAMM([&](AMM& ammAlice, Env&) {
+
2254 auto const carolTokens = ammAlice.deposit(carol, USD(1'000));
+
2255 auto const aliceTokens = ammAlice.deposit(alice, USD(1'000));
+
2256 ammAlice.withdraw(alice, aliceTokens, USD(0));
+
2257 ammAlice.withdraw(carol, carolTokens, USD(0));
+
2258 BEAST_EXPECT(ammAlice.expectBalances(
+
2259 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
2260 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
2261 BEAST_EXPECT(ammAlice.expectLPTokens(alice, ammAlice.tokens()));
+
2262 });
+
2263
+
2264 // Equal deposit 10%, withdraw all tokens
+
2265 testAMM([&](AMM& ammAlice, Env&) {
+
2266 ammAlice.deposit(carol, 1'000'000);
+
2267 ammAlice.withdrawAll(carol);
+
2268 BEAST_EXPECT(ammAlice.expectBalances(
+
2269 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
+
2270 });
+
2271
+
2272 // Equal deposit 10%, withdraw all tokens in USD
+
2273 testAMM([&](AMM& ammAlice, Env&) {
+
2274 ammAlice.deposit(carol, 1'000'000);
+
2275 ammAlice.withdrawAll(carol, USD(0));
+
2276 BEAST_EXPECT(ammAlice.expectBalances(
+
2277 XRP(11'000),
+
2278 STAmount{USD, UINT64_C(9'090'909090909092), -12},
+
2279 IOUAmount{10'000'000, 0}));
+
2280 });
+
2281
+
2282 // Equal deposit 10%, withdraw all tokens in XRP
+
2283 testAMM([&](AMM& ammAlice, Env&) {
+
2284 ammAlice.deposit(carol, 1'000'000);
+
2285 ammAlice.withdrawAll(carol, XRP(0));
+
2286 BEAST_EXPECT(ammAlice.expectBalances(
+
2287 XRPAmount(9'090'909'091),
+
2288 USD(11'000),
+
2289 IOUAmount{10'000'000, 0}));
+
2290 });
+
2291
+
2292 auto const all = supported_amendments();
+
2293 // Withdraw with EPrice limit.
+
2294 testAMM(
+
2295 [&](AMM& ammAlice, Env& env) {
+
2296 ammAlice.deposit(carol, 1'000'000);
+
2297 ammAlice.withdraw(
+
2298 carol, USD(100), std::nullopt, IOUAmount{520, 0});
+
2299 if (!env.current()->rules().enabled(fixAMMv1_1))
+
2300 BEAST_EXPECT(
+
2301 ammAlice.expectBalances(
+
2302 XRPAmount(11'000'000'000),
+
2303 STAmount{USD, UINT64_C(9'372'781065088757), -12},
+
2304 IOUAmount{10'153'846'15384616, -8}) &&
+
2305 ammAlice.expectLPTokens(
+
2306 carol, IOUAmount{153'846'15384616, -8}));
+
2307 else
+
2308 BEAST_EXPECT(
+
2309 ammAlice.expectBalances(
+
2310 XRPAmount(11'000'000'000),
+
2311 STAmount{USD, UINT64_C(9'372'781065088769), -12},
+
2312 IOUAmount{10'153'846'15384616, -8}) &&
+
2313 ammAlice.expectLPTokens(
+
2314 carol, IOUAmount{153'846'15384616, -8}));
+
2315 ammAlice.withdrawAll(carol);
+
2316 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
2317 },
+
2318 std::nullopt,
+
2319 0,
+
2320 std::nullopt,
+
2321 {all, all - fixAMMv1_1});
+
2322
+
2323 // Withdraw with EPrice limit. AssetOut is 0.
+
2324 testAMM(
+
2325 [&](AMM& ammAlice, Env& env) {
+
2326 ammAlice.deposit(carol, 1'000'000);
+
2327 ammAlice.withdraw(
+
2328 carol, USD(0), std::nullopt, IOUAmount{520, 0});
+
2329 if (!env.current()->rules().enabled(fixAMMv1_1))
+
2330 BEAST_EXPECT(
+
2331 ammAlice.expectBalances(
+
2332 XRPAmount(11'000'000'000),
+
2333 STAmount{USD, UINT64_C(9'372'781065088757), -12},
+
2334 IOUAmount{10'153'846'15384616, -8}) &&
+
2335 ammAlice.expectLPTokens(
+
2336 carol, IOUAmount{153'846'15384616, -8}));
+
2337 else
+
2338 BEAST_EXPECT(
+
2339 ammAlice.expectBalances(
+
2340 XRPAmount(11'000'000'000),
+
2341 STAmount{USD, UINT64_C(9'372'781065088769), -12},
+
2342 IOUAmount{10'153'846'15384616, -8}) &&
+
2343 ammAlice.expectLPTokens(
+
2344 carol, IOUAmount{153'846'15384616, -8}));
+
2345 },
+
2346 std::nullopt,
+
2347 0,
+
2348 std::nullopt,
+
2349 {all, all - fixAMMv1_1});
+
2350
+
2351 // IOU to IOU + transfer fee
+
2352 {
+
2353 Env env{*this};
+
2354 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
+
2355 env(rate(gw, 1.25));
+
2356 env.close();
+
2357 // no transfer fee on create
+
2358 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
+
2359 BEAST_EXPECT(ammAlice.expectBalances(
+
2360 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
+
2361 BEAST_EXPECT(expectLine(env, alice, USD(0)));
+
2362 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
+
2363 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
+
2364 // no transfer fee on deposit
+
2365 ammAlice.deposit(carol, 10);
+
2366 BEAST_EXPECT(ammAlice.expectBalances(
+
2367 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
+
2368 BEAST_EXPECT(expectLine(env, carol, USD(0)));
+
2369 BEAST_EXPECT(expectLine(env, carol, BTC(0)));
+
2370 // no transfer fee on withdraw
+
2371 ammAlice.withdraw(carol, 10);
+
2372 BEAST_EXPECT(ammAlice.expectBalances(
+
2373 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
+
2374 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
+
2375 BEAST_EXPECT(expectLine(env, carol, USD(2'000)));
+
2376 BEAST_EXPECT(expectLine(env, carol, BTC(0.05)));
+
2377 }
+
2378
+
2379 // Tiny withdraw
+
2380 testAMM([&](AMM& ammAlice, Env&) {
+
2381 // By tokens
+
2382 ammAlice.withdraw(alice, IOUAmount{1, -3});
+
2383 BEAST_EXPECT(ammAlice.expectBalances(
+
2384 XRPAmount{9'999'999'999},
+
2385 STAmount{USD, UINT64_C(9'999'999999), -6},
+
2386 IOUAmount{9'999'999'999, -3}));
+
2387 });
+
2388 testAMM([&](AMM& ammAlice, Env&) {
+
2389 // Single XRP pool
+
2390 ammAlice.withdraw(alice, std::nullopt, XRPAmount{1});
+
2391 BEAST_EXPECT(ammAlice.expectBalances(
+
2392 XRPAmount{9'999'999'999},
+
2393 USD(10'000),
+
2394 IOUAmount{9'999'999'9995, -4}));
+
2395 });
+
2396 testAMM([&](AMM& ammAlice, Env&) {
+
2397 // Single USD pool
+
2398 ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
+
2399 BEAST_EXPECT(ammAlice.expectBalances(
+
2400 XRP(10'000),
+
2401 STAmount{USD, UINT64_C(9'999'9999999999), -10},
+
2402 IOUAmount{9'999'999'99999995, -8}));
+
2403 });
+
2404
+
2405 // Withdraw close to entire pool
+
2406 // Equal by tokens
+
2407 testAMM([&](AMM& ammAlice, Env&) {
+
2408 ammAlice.withdraw(alice, IOUAmount{9'999'999'999, -3});
+
2409 BEAST_EXPECT(ammAlice.expectBalances(
+
2410 XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
+
2411 });
+
2412 // USD by tokens
+
2413 testAMM([&](AMM& ammAlice, Env&) {
+
2414 ammAlice.withdraw(alice, IOUAmount{9'999'999}, USD(0));
+
2415 BEAST_EXPECT(ammAlice.expectBalances(
+
2416 XRP(10'000), STAmount{USD, 1, -10}, IOUAmount{1}));
+
2417 });
+
2418 // XRP by tokens
+
2419 testAMM([&](AMM& ammAlice, Env&) {
+
2420 ammAlice.withdraw(alice, IOUAmount{9'999'900}, XRP(0));
+
2421 BEAST_EXPECT(ammAlice.expectBalances(
+
2422 XRPAmount{1}, USD(10'000), IOUAmount{100}));
+
2423 });
+
2424 // USD
+
2425 testAMM([&](AMM& ammAlice, Env&) {
+
2426 ammAlice.withdraw(
+
2427 alice, STAmount{USD, UINT64_C(9'999'99999999999), -11});
+
2428 BEAST_EXPECT(ammAlice.expectBalances(
+
2429 XRP(10000), STAmount{USD, 1, -11}, IOUAmount{316227765, -9}));
+
2430 });
+
2431 // XRP
+
2432 testAMM([&](AMM& ammAlice, Env&) {
+
2433 ammAlice.withdraw(alice, XRPAmount{9'999'999'999});
+
2434 BEAST_EXPECT(ammAlice.expectBalances(
+
2435 XRPAmount{1}, USD(10'000), IOUAmount{100}));
+
2436 });
+
2437 }
+
2438
+
2439 void
+
2440 testInvalidFeeVote()
+
2441 {
+
2442 testcase("Invalid Fee Vote");
+
2443 using namespace jtx;
+
2444
+
2445 testAMM([&](AMM& ammAlice, Env& env) {
+
2446 // Invalid flags
+
2447 ammAlice.vote(
+
2448 std::nullopt,
+
2449 1'000,
+
2450 tfWithdrawAll,
2451 std::nullopt,
-
2452 ter(temINVALID_FLAG));
-
2453
-
2454 // Invalid fee.
-
2455 ammAlice.vote(
-
2456 std::nullopt,
-
2457 1'001,
-
2458 std::nullopt,
+
2452 std::nullopt,
+
2453 ter(temINVALID_FLAG));
+
2454
+
2455 // Invalid fee.
+
2456 ammAlice.vote(
+
2457 std::nullopt,
+
2458 1'001,
2459 std::nullopt,
2460 std::nullopt,
-
2461 ter(temBAD_FEE));
-
2462 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
2463
-
2464 // Invalid Account
-
2465 Account bad("bad");
-
2466 env.memoize(bad);
-
2467 ammAlice.vote(
-
2468 bad,
-
2469 1'000,
-
2470 std::nullopt,
-
2471 seq(1),
-
2472 std::nullopt,
-
2473 ter(terNO_ACCOUNT));
-
2474
-
2475 // Invalid AMM
-
2476 ammAlice.vote(
-
2477 alice,
-
2478 1'000,
-
2479 std::nullopt,
+
2461 std::nullopt,
+
2462 ter(temBAD_FEE));
+
2463 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
2464
+
2465 // Invalid Account
+
2466 Account bad("bad");
+
2467 env.memoize(bad);
+
2468 ammAlice.vote(
+
2469 bad,
+
2470 1'000,
+
2471 std::nullopt,
+
2472 seq(1),
+
2473 std::nullopt,
+
2474 ter(terNO_ACCOUNT));
+
2475
+
2476 // Invalid AMM
+
2477 ammAlice.vote(
+
2478 alice,
+
2479 1'000,
2480 std::nullopt,
-
2481 {{USD, GBP}},
-
2482 ter(terNO_AMM));
-
2483
-
2484 // Account is not LP
-
2485 ammAlice.vote(
-
2486 carol,
-
2487 1'000,
-
2488 std::nullopt,
+
2481 std::nullopt,
+
2482 {{USD, GBP}},
+
2483 ter(terNO_AMM));
+
2484
+
2485 // Account is not LP
+
2486 ammAlice.vote(
+
2487 carol,
+
2488 1'000,
2489 std::nullopt,
2490 std::nullopt,
-
2491 ter(tecAMM_INVALID_TOKENS));
-
2492 });
-
2493
-
2494 // Invalid AMM
-
2495 testAMM([&](AMM& ammAlice, Env& env) {
-
2496 ammAlice.withdrawAll(alice);
-
2497 ammAlice.vote(
-
2498 alice,
-
2499 1'000,
-
2500 std::nullopt,
+
2491 std::nullopt,
+
2492 ter(tecAMM_INVALID_TOKENS));
+
2493 });
+
2494
+
2495 // Invalid AMM
+
2496 testAMM([&](AMM& ammAlice, Env& env) {
+
2497 ammAlice.withdrawAll(alice);
+
2498 ammAlice.vote(
+
2499 alice,
+
2500 1'000,
2501 std::nullopt,
2502 std::nullopt,
-
2503 ter(terNO_AMM));
-
2504 });
-
2505 }
-
2506
-
2507 void
-
2508 testFeeVote()
-
2509 {
-
2510 testcase("Fee Vote");
-
2511 using namespace jtx;
-
2512
-
2513 // One vote sets fee to 1%.
-
2514 testAMM([&](AMM& ammAlice, Env& env) {
-
2515 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{0}));
-
2516 ammAlice.vote({}, 1'000);
-
2517 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
-
2518 // Discounted fee is 1/10 of trading fee.
-
2519 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{0}));
-
2520 });
-
2521
-
2522 auto vote = [&](AMM& ammAlice,
-
2523 Env& env,
-
2524 int i,
-
2525 int fundUSD = 100'000,
-
2526 std::uint32_t tokens = 10'000'000,
-
2527 std::vector<Account>* accounts = nullptr) {
-
2528 Account a(std::to_string(i));
-
2529 fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
-
2530 ammAlice.deposit(a, tokens);
-
2531 ammAlice.vote(a, 50 * (i + 1));
-
2532 if (accounts)
-
2533 accounts->push_back(std::move(a));
-
2534 };
-
2535
-
2536 // Eight votes fill all voting slots, set fee 0.175%.
-
2537 testAMM([&](AMM& ammAlice, Env& env) {
-
2538 for (int i = 0; i < 7; ++i)
-
2539 vote(ammAlice, env, i, 10'000);
-
2540 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
2541 });
-
2542
-
2543 // Eight votes fill all voting slots, set fee 0.175%.
-
2544 // New vote, same account, sets fee 0.225%
-
2545 testAMM([&](AMM& ammAlice, Env& env) {
-
2546 for (int i = 0; i < 7; ++i)
-
2547 vote(ammAlice, env, i);
-
2548 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
2549 Account const a("0");
-
2550 ammAlice.vote(a, 450);
-
2551 BEAST_EXPECT(ammAlice.expectTradingFee(225));
-
2552 });
-
2553
-
2554 // Eight votes fill all voting slots, set fee 0.175%.
-
2555 // New vote, new account, higher vote weight, set higher fee 0.244%
-
2556 testAMM([&](AMM& ammAlice, Env& env) {
-
2557 for (int i = 0; i < 7; ++i)
-
2558 vote(ammAlice, env, i);
-
2559 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
2560 vote(ammAlice, env, 7, 100'000, 20'000'000);
-
2561 BEAST_EXPECT(ammAlice.expectTradingFee(244));
-
2562 });
-
2563
-
2564 // Eight votes fill all voting slots, set fee 0.219%.
-
2565 // New vote, new account, higher vote weight, set smaller fee 0.206%
-
2566 testAMM([&](AMM& ammAlice, Env& env) {
-
2567 for (int i = 7; i > 0; --i)
-
2568 vote(ammAlice, env, i);
-
2569 BEAST_EXPECT(ammAlice.expectTradingFee(219));
-
2570 vote(ammAlice, env, 0, 100'000, 20'000'000);
-
2571 BEAST_EXPECT(ammAlice.expectTradingFee(206));
-
2572 });
-
2573
-
2574 // Eight votes fill all voting slots. The accounts then withdraw all
-
2575 // tokens. An account sets a new fee and the previous slots are
-
2576 // deleted.
-
2577 testAMM([&](AMM& ammAlice, Env& env) {
-
2578 std::vector<Account> accounts;
-
2579 for (int i = 0; i < 7; ++i)
-
2580 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
-
2581 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
2582 for (int i = 0; i < 7; ++i)
-
2583 ammAlice.withdrawAll(accounts[i]);
-
2584 ammAlice.deposit(carol, 10'000'000);
-
2585 ammAlice.vote(carol, 1'000);
-
2586 // The initial LP set the fee to 1000. Carol gets 50% voting
-
2587 // power, and the new fee is 500.
-
2588 BEAST_EXPECT(ammAlice.expectTradingFee(500));
-
2589 });
-
2590
-
2591 // Eight votes fill all voting slots. The accounts then withdraw some
-
2592 // tokens. The new vote doesn't get the voting power but
-
2593 // the slots are refreshed and the fee is updated.
-
2594 testAMM([&](AMM& ammAlice, Env& env) {
-
2595 std::vector<Account> accounts;
-
2596 for (int i = 0; i < 7; ++i)
-
2597 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
-
2598 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
2599 for (int i = 0; i < 7; ++i)
-
2600 ammAlice.withdraw(accounts[i], 9'000'000);
-
2601 ammAlice.deposit(carol, 1'000);
-
2602 // The vote is not added to the slots
-
2603 ammAlice.vote(carol, 1'000);
-
2604 auto const info = ammAlice.ammRpcInfo()[jss::amm][jss::vote_slots];
-
2605 for (std::uint16_t i = 0; i < info.size(); ++i)
-
2606 BEAST_EXPECT(info[i][jss::account] != carol.human());
-
2607 // But the slots are refreshed and the fee is changed
-
2608 BEAST_EXPECT(ammAlice.expectTradingFee(82));
-
2609 });
-
2610 }
-
2611
-
2612 void
-
2613 testInvalidBid()
-
2614 {
-
2615 testcase("Invalid Bid");
-
2616 using namespace jtx;
-
2617 using namespace std::chrono;
-
2618
-
2619 // burn all the LPTokens through a AMMBid transaction
-
2620 {
-
2621 Env env(*this);
-
2622 fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
-
2623 AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
-
2624
-
2625 // auction slot is owned by the creator of the AMM i.e. gw
-
2626 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
-
2627
-
2628 // gw attempts to burn all her LPTokens through a bid transaction
-
2629 // this transaction fails because AMMBid transaction can not burn
-
2630 // all the outstanding LPTokens
-
2631 env(amm.bid({
-
2632 .account = gw,
-
2633 .bidMin = 1'000'000,
-
2634 }),
-
2635 ter(tecAMM_INVALID_TOKENS));
-
2636 }
-
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 burns all but one of its LPTokens through a bid transaction
-
2648 // this transaction suceeds because the bid price is less than
-
2649 // the total outstanding LPToken balance
-
2650 env(amm.bid({
-
2651 .account = gw,
-
2652 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
-
2653 }),
-
2654 ter(tesSUCCESS))
-
2655 .close();
-
2656
-
2657 // gw must own the auction slot
-
2658 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{999'999}));
-
2659
-
2660 // 999'999 tokens are burned, only 1 LPToken is owned by gw
-
2661 BEAST_EXPECT(
-
2662 amm.expectBalances(XRP(1'000), USD(1'000), IOUAmount{1}));
-
2663
-
2664 // gw owns only 1 LPToken in its balance
-
2665 BEAST_EXPECT(Number{amm.getLPTokensBalance(gw)} == 1);
-
2666
-
2667 // gw attempts to burn the last of its LPTokens in an AMMBid
-
2668 // transaction. This transaction fails because it would burn all
-
2669 // the remaining LPTokens
-
2670 env(amm.bid({
-
2671 .account = gw,
-
2672 .bidMin = 1,
-
2673 }),
-
2674 ter(tecAMM_INVALID_TOKENS));
-
2675 }
-
2676
-
2677 testAMM([&](AMM& ammAlice, Env& env) {
-
2678 // Invalid flags
-
2679 env(ammAlice.bid({
-
2680 .account = carol,
-
2681 .bidMin = 0,
-
2682 .flags = tfWithdrawAll,
-
2683 }),
-
2684 ter(temINVALID_FLAG));
-
2685
-
2686 ammAlice.deposit(carol, 1'000'000);
-
2687 // Invalid Bid price <= 0
-
2688 for (auto bid : {0, -100})
-
2689 {
-
2690 env(ammAlice.bid({
-
2691 .account = carol,
-
2692 .bidMin = bid,
-
2693 }),
-
2694 ter(temBAD_AMOUNT));
-
2695 env(ammAlice.bid({
-
2696 .account = carol,
-
2697 .bidMax = bid,
-
2698 }),
-
2699 ter(temBAD_AMOUNT));
-
2700 }
-
2701
-
2702 // Invlaid Min/Max combination
-
2703 env(ammAlice.bid({
-
2704 .account = carol,
-
2705 .bidMin = 200,
-
2706 .bidMax = 100,
-
2707 }),
-
2708 ter(tecAMM_INVALID_TOKENS));
-
2709
-
2710 // Invalid Account
-
2711 Account bad("bad");
-
2712 env.memoize(bad);
-
2713 env(ammAlice.bid({
-
2714 .account = bad,
-
2715 .bidMax = 100,
-
2716 }),
-
2717 seq(1),
-
2718 ter(terNO_ACCOUNT));
-
2719
-
2720 // Account is not LP
-
2721 Account const dan("dan");
-
2722 env.fund(XRP(1'000), dan);
-
2723 env(ammAlice.bid({
-
2724 .account = dan,
-
2725 .bidMin = 100,
-
2726 }),
-
2727 ter(tecAMM_INVALID_TOKENS));
-
2728 env(ammAlice.bid({
-
2729 .account = dan,
-
2730 }),
-
2731 ter(tecAMM_INVALID_TOKENS));
-
2732
-
2733 // Auth account is invalid.
-
2734 env(ammAlice.bid({
-
2735 .account = carol,
-
2736 .bidMin = 100,
-
2737 .authAccounts = {bob},
-
2738 }),
-
2739 ter(terNO_ACCOUNT));
-
2740
-
2741 // Invalid Assets
-
2742 env(ammAlice.bid({
-
2743 .account = alice,
-
2744 .bidMax = 100,
-
2745 .assets = {{USD, GBP}},
-
2746 }),
-
2747 ter(terNO_AMM));
-
2748
-
2749 // Invalid Min/Max issue
-
2750 env(ammAlice.bid({
-
2751 .account = alice,
-
2752 .bidMax = STAmount{USD, 100},
-
2753 }),
-
2754 ter(temBAD_AMM_TOKENS));
-
2755 env(ammAlice.bid({
-
2756 .account = alice,
-
2757 .bidMin = STAmount{USD, 100},
-
2758 }),
-
2759 ter(temBAD_AMM_TOKENS));
-
2760 });
-
2761
-
2762 // Invalid AMM
-
2763 testAMM([&](AMM& ammAlice, Env& env) {
-
2764 ammAlice.withdrawAll(alice);
-
2765 env(ammAlice.bid({
-
2766 .account = alice,
-
2767 .bidMax = 100,
-
2768 }),
-
2769 ter(terNO_AMM));
-
2770 });
-
2771
-
2772 // More than four Auth accounts.
-
2773 testAMM([&](AMM& ammAlice, Env& env) {
-
2774 Account ed("ed");
-
2775 Account bill("bill");
-
2776 Account scott("scott");
-
2777 Account james("james");
-
2778 env.fund(XRP(1'000), bob, ed, bill, scott, james);
-
2779 env.close();
-
2780 ammAlice.deposit(carol, 1'000'000);
-
2781 env(ammAlice.bid({
-
2782 .account = carol,
-
2783 .bidMin = 100,
-
2784 .authAccounts = {bob, ed, bill, scott, james},
-
2785 }),
-
2786 ter(temMALFORMED));
-
2787 });
-
2788
-
2789 // Bid price exceeds LP owned tokens
-
2790 testAMM([&](AMM& ammAlice, Env& env) {
-
2791 fund(env, gw, {bob}, XRP(1'000), {USD(100)}, Fund::Acct);
-
2792 ammAlice.deposit(carol, 1'000'000);
-
2793 ammAlice.deposit(bob, 10);
-
2794 env(ammAlice.bid({
-
2795 .account = carol,
-
2796 .bidMin = 1'000'001,
-
2797 }),
-
2798 ter(tecAMM_INVALID_TOKENS));
-
2799 env(ammAlice.bid({
-
2800 .account = carol,
-
2801 .bidMax = 1'000'001,
-
2802 }),
-
2803 ter(tecAMM_INVALID_TOKENS));
-
2804 env(ammAlice.bid({
-
2805 .account = carol,
-
2806 .bidMin = 1'000,
-
2807 }));
-
2808 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
-
2809 // Slot purchase price is more than 1000 but bob only has 10 tokens
-
2810 env(ammAlice.bid({
-
2811 .account = bob,
-
2812 }),
-
2813 ter(tecAMM_INVALID_TOKENS));
-
2814 });
-
2815
-
2816 // Bid all tokens, still own the slot
-
2817 {
-
2818 Env env(*this);
-
2819 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'000)});
-
2820 AMM amm(env, gw, XRP(10), USD(1'000));
-
2821 auto const lpIssue = amm.lptIssue();
-
2822 env.trust(STAmount{lpIssue, 100}, alice);
-
2823 env.trust(STAmount{lpIssue, 50}, bob);
-
2824 env(pay(gw, alice, STAmount{lpIssue, 100}));
-
2825 env(pay(gw, bob, STAmount{lpIssue, 50}));
-
2826 env(amm.bid({.account = alice, .bidMin = 100}));
-
2827 // Alice doesn't have any more tokens, but
-
2828 // she still owns the slot.
-
2829 env(amm.bid({
-
2830 .account = bob,
-
2831 .bidMax = 50,
-
2832 }),
-
2833 ter(tecAMM_FAILED));
-
2834 }
-
2835 }
-
2836
-
2837 void
-
2838 testBid(FeatureBitset features)
-
2839 {
-
2840 testcase("Bid");
-
2841 using namespace jtx;
-
2842 using namespace std::chrono;
-
2843
-
2844 // Auction slot initially is owned by AMM creator, who pays 0 price.
-
2845
-
2846 // Bid 110 tokens. Pay bidMin.
-
2847 testAMM(
-
2848 [&](AMM& ammAlice, Env& env) {
-
2849 ammAlice.deposit(carol, 1'000'000);
-
2850 env(ammAlice.bid({.account = carol, .bidMin = 110}));
-
2851 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
-
2852 // 110 tokens are burned.
-
2853 BEAST_EXPECT(ammAlice.expectBalances(
-
2854 XRP(11'000), USD(11'000), IOUAmount{10'999'890, 0}));
-
2855 },
-
2856 std::nullopt,
-
2857 0,
-
2858 std::nullopt,
-
2859 {features});
-
2860
-
2861 // Bid with min/max when the pay price is less than min.
-
2862 testAMM(
-
2863 [&](AMM& ammAlice, Env& env) {
-
2864 ammAlice.deposit(carol, 1'000'000);
-
2865 // Bid exactly 110. Pay 110 because the pay price is < 110.
-
2866 env(ammAlice.bid(
-
2867 {.account = carol, .bidMin = 110, .bidMax = 110}));
-
2868 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
-
2869 BEAST_EXPECT(ammAlice.expectBalances(
-
2870 XRP(11'000), USD(11'000), IOUAmount{10'999'890}));
-
2871 // Bid exactly 180-200. Pay 180 because the pay price is < 180.
-
2872 env(ammAlice.bid(
-
2873 {.account = alice, .bidMin = 180, .bidMax = 200}));
-
2874 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{180}));
-
2875 BEAST_EXPECT(ammAlice.expectBalances(
-
2876 XRP(11'000), USD(11'000), IOUAmount{10'999'814'5, -1}));
-
2877 },
-
2878 std::nullopt,
-
2879 0,
-
2880 std::nullopt,
-
2881 {features});
-
2882
-
2883 // Start bid at bidMin 110.
-
2884 testAMM(
-
2885 [&](AMM& ammAlice, Env& env) {
-
2886 ammAlice.deposit(carol, 1'000'000);
-
2887 // Bid, pay bidMin.
-
2888 env(ammAlice.bid({.account = carol, .bidMin = 110}));
-
2889 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
-
2890
-
2891 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
-
2892 ammAlice.deposit(bob, 1'000'000);
-
2893 // Bid, pay the computed price.
-
2894 env(ammAlice.bid({.account = bob}));
-
2895 BEAST_EXPECT(
-
2896 ammAlice.expectAuctionSlot(0, 0, IOUAmount(1155, -1)));
-
2897
-
2898 // Bid bidMax fails because the computed price is higher.
-
2899 env(ammAlice.bid({
-
2900 .account = carol,
-
2901 .bidMax = 120,
-
2902 }),
-
2903 ter(tecAMM_FAILED));
-
2904 // Bid MaxSlotPrice succeeds - pay computed price
-
2905 env(ammAlice.bid({.account = carol, .bidMax = 600}));
-
2906 BEAST_EXPECT(
-
2907 ammAlice.expectAuctionSlot(0, 0, IOUAmount{121'275, -3}));
-
2908
-
2909 // Bid Min/MaxSlotPrice fails because the computed price is not
-
2910 // in range
-
2911 env(ammAlice.bid({
-
2912 .account = carol,
-
2913 .bidMin = 10,
-
2914 .bidMax = 100,
-
2915 }),
-
2916 ter(tecAMM_FAILED));
-
2917 // Bid Min/MaxSlotPrice succeeds - pay computed price
-
2918 env(ammAlice.bid(
-
2919 {.account = carol, .bidMin = 100, .bidMax = 600}));
-
2920 BEAST_EXPECT(
-
2921 ammAlice.expectAuctionSlot(0, 0, IOUAmount{127'33875, -5}));
-
2922 },
-
2923 std::nullopt,
-
2924 0,
-
2925 std::nullopt,
-
2926 {features});
-
2927
-
2928 // Slot states.
-
2929 testAMM(
-
2930 [&](AMM& ammAlice, Env& env) {
-
2931 ammAlice.deposit(carol, 1'000'000);
-
2932
-
2933 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
-
2934 ammAlice.deposit(bob, 1'000'000);
-
2935 BEAST_EXPECT(ammAlice.expectBalances(
-
2936 XRP(12'000), USD(12'000), IOUAmount{12'000'000, 0}));
-
2937
-
2938 // Initial state. Pay bidMin.
-
2939 env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
-
2940 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
-
2941
-
2942 // 1st Interval after close, price for 0th interval.
-
2943 env(ammAlice.bid({.account = bob}));
-
2944 env.close(seconds(AUCTION_SLOT_INTERVAL_DURATION + 1));
-
2945 BEAST_EXPECT(
-
2946 ammAlice.expectAuctionSlot(0, 1, IOUAmount{1'155, -1}));
-
2947
-
2948 // 10th Interval after close, price for 1st interval.
-
2949 env(ammAlice.bid({.account = carol}));
-
2950 env.close(seconds(10 * AUCTION_SLOT_INTERVAL_DURATION + 1));
-
2951 BEAST_EXPECT(
-
2952 ammAlice.expectAuctionSlot(0, 10, IOUAmount{121'275, -3}));
-
2953
-
2954 // 20th Interval (expired) after close, price for 10th interval.
-
2955 env(ammAlice.bid({.account = bob}));
-
2956 env.close(seconds(
-
2957 AUCTION_SLOT_TIME_INTERVALS *
-
2958 AUCTION_SLOT_INTERVAL_DURATION +
-
2959 1));
-
2960 BEAST_EXPECT(ammAlice.expectAuctionSlot(
-
2961 0, std::nullopt, IOUAmount{127'33875, -5}));
-
2962
-
2963 // 0 Interval.
-
2964 env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
-
2965 BEAST_EXPECT(ammAlice.expectAuctionSlot(
-
2966 0, std::nullopt, IOUAmount{110}));
-
2967 // ~321.09 tokens burnt on bidding fees.
-
2968 BEAST_EXPECT(ammAlice.expectBalances(
-
2969 XRP(12'000), USD(12'000), IOUAmount{11'999'678'91, -2}));
-
2970 },
-
2971 std::nullopt,
-
2972 0,
-
2973 std::nullopt,
-
2974 {features});
-
2975
-
2976 // Pool's fee 1%. Bid bidMin.
-
2977 // Auction slot owner and auth account trade at discounted fee -
-
2978 // 1/10 of the trading fee.
-
2979 // Other accounts trade at 1% fee.
-
2980 testAMM(
-
2981 [&](AMM& ammAlice, Env& env) {
-
2982 Account const dan("dan");
-
2983 Account const ed("ed");
-
2984 fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::Acct);
-
2985 ammAlice.deposit(bob, 1'000'000);
-
2986 ammAlice.deposit(ed, 1'000'000);
-
2987 ammAlice.deposit(carol, 500'000);
-
2988 ammAlice.deposit(dan, 500'000);
-
2989 auto ammTokens = ammAlice.getLPTokensBalance();
-
2990 env(ammAlice.bid({
-
2991 .account = carol,
-
2992 .bidMin = 120,
-
2993 .authAccounts = {bob, ed},
-
2994 }));
-
2995 auto const slotPrice = IOUAmount{5'200};
-
2996 ammTokens -= slotPrice;
-
2997 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, slotPrice));
-
2998 BEAST_EXPECT(ammAlice.expectBalances(
-
2999 XRP(13'000), USD(13'000), ammTokens));
-
3000 // Discounted trade
-
3001 for (int i = 0; i < 10; ++i)
-
3002 {
-
3003 auto tokens = ammAlice.deposit(carol, USD(100));
-
3004 ammAlice.withdraw(carol, tokens, USD(0));
-
3005 tokens = ammAlice.deposit(bob, USD(100));
-
3006 ammAlice.withdraw(bob, tokens, USD(0));
-
3007 tokens = ammAlice.deposit(ed, USD(100));
-
3008 ammAlice.withdraw(ed, tokens, USD(0));
-
3009 }
-
3010 // carol, bob, and ed pay ~0.99USD in fees.
-
3011 if (!features[fixAMMv1_1])
-
3012 {
-
3013 BEAST_EXPECT(
-
3014 env.balance(carol, USD) ==
-
3015 STAmount(USD, UINT64_C(29'499'00572620545), -11));
-
3016 BEAST_EXPECT(
-
3017 env.balance(bob, USD) ==
-
3018 STAmount(USD, UINT64_C(18'999'00572616195), -11));
-
3019 BEAST_EXPECT(
-
3020 env.balance(ed, USD) ==
-
3021 STAmount(USD, UINT64_C(18'999'00572611841), -11));
-
3022 // USD pool is slightly higher because of the fees.
-
3023 BEAST_EXPECT(ammAlice.expectBalances(
-
3024 XRP(13'000),
-
3025 STAmount(USD, UINT64_C(13'002'98282151419), -11),
-
3026 ammTokens));
-
3027 }
-
3028 else
-
3029 {
-
3030 BEAST_EXPECT(
-
3031 env.balance(carol, USD) ==
-
3032 STAmount(USD, UINT64_C(29'499'00572620544), -11));
-
3033 BEAST_EXPECT(
-
3034 env.balance(bob, USD) ==
-
3035 STAmount(USD, UINT64_C(18'999'00572616194), -11));
-
3036 BEAST_EXPECT(
-
3037 env.balance(ed, USD) ==
-
3038 STAmount(USD, UINT64_C(18'999'0057261184), -10));
-
3039 // USD pool is slightly higher because of the fees.
-
3040 BEAST_EXPECT(ammAlice.expectBalances(
-
3041 XRP(13'000),
-
3042 STAmount(USD, UINT64_C(13'002'98282151422), -11),
-
3043 ammTokens));
-
3044 }
-
3045 ammTokens = ammAlice.getLPTokensBalance();
-
3046 // Trade with the fee
-
3047 for (int i = 0; i < 10; ++i)
-
3048 {
-
3049 auto const tokens = ammAlice.deposit(dan, USD(100));
-
3050 ammAlice.withdraw(dan, tokens, USD(0));
-
3051 }
-
3052 // dan pays ~9.94USD, which is ~10 times more in fees than
-
3053 // carol, bob, ed. the discounted fee is 10 times less
-
3054 // than the trading fee.
-
3055 if (!features[fixAMMv1_1])
-
3056 {
-
3057 BEAST_EXPECT(
-
3058 env.balance(dan, USD) ==
-
3059 STAmount(USD, UINT64_C(19'490'056722744), -9));
-
3060 // USD pool gains more in dan's fees.
-
3061 BEAST_EXPECT(ammAlice.expectBalances(
-
3062 XRP(13'000),
-
3063 STAmount{USD, UINT64_C(13'012'92609877019), -11},
-
3064 ammTokens));
-
3065 // Discounted fee payment
-
3066 ammAlice.deposit(carol, USD(100));
-
3067 ammTokens = ammAlice.getLPTokensBalance();
-
3068 BEAST_EXPECT(ammAlice.expectBalances(
-
3069 XRP(13'000),
-
3070 STAmount{USD, UINT64_C(13'112'92609877019), -11},
-
3071 ammTokens));
-
3072 env(pay(carol, bob, USD(100)),
-
3073 path(~USD),
-
3074 sendmax(XRP(110)));
-
3075 env.close();
-
3076 // carol pays 100000 drops in fees
-
3077 // 99900668XRP swapped in for 100USD
-
3078 BEAST_EXPECT(ammAlice.expectBalances(
-
3079 XRPAmount{13'100'000'668},
-
3080 STAmount{USD, UINT64_C(13'012'92609877019), -11},
-
3081 ammTokens));
-
3082 }
-
3083 else
-
3084 {
-
3085 BEAST_EXPECT(
-
3086 env.balance(dan, USD) ==
-
3087 STAmount(USD, UINT64_C(19'490'05672274399), -11));
-
3088 // USD pool gains more in dan's fees.
-
3089 BEAST_EXPECT(ammAlice.expectBalances(
-
3090 XRP(13'000),
-
3091 STAmount{USD, UINT64_C(13'012'92609877023), -11},
-
3092 ammTokens));
-
3093 // Discounted fee payment
-
3094 ammAlice.deposit(carol, USD(100));
-
3095 ammTokens = ammAlice.getLPTokensBalance();
-
3096 BEAST_EXPECT(ammAlice.expectBalances(
-
3097 XRP(13'000),
-
3098 STAmount{USD, UINT64_C(13'112'92609877023), -11},
-
3099 ammTokens));
-
3100 env(pay(carol, bob, USD(100)),
-
3101 path(~USD),
-
3102 sendmax(XRP(110)));
-
3103 env.close();
-
3104 // carol pays 100000 drops in fees
-
3105 // 99900668XRP swapped in for 100USD
-
3106 BEAST_EXPECT(ammAlice.expectBalances(
-
3107 XRPAmount{13'100'000'668},
-
3108 STAmount{USD, UINT64_C(13'012'92609877023), -11},
-
3109 ammTokens));
-
3110 }
-
3111 // Payment with the trading fee
-
3112 env(pay(alice, carol, XRP(100)), path(~XRP), sendmax(USD(110)));
-
3113 env.close();
-
3114 // alice pays ~1.011USD in fees, which is ~10 times more
-
3115 // than carol's fee
-
3116 // 100.099431529USD swapped in for 100XRP
-
3117 if (!features[fixAMMv1_1])
-
3118 {
-
3119 BEAST_EXPECT(ammAlice.expectBalances(
-
3120 XRPAmount{13'000'000'668},
-
3121 STAmount{USD, UINT64_C(13'114'03663047264), -11},
-
3122 ammTokens));
-
3123 }
-
3124 else
-
3125 {
-
3126 BEAST_EXPECT(ammAlice.expectBalances(
-
3127 XRPAmount{13'000'000'668},
-
3128 STAmount{USD, UINT64_C(13'114'03663047269), -11},
-
3129 ammTokens));
-
3130 }
-
3131 // Auction slot expired, no discounted fee
-
3132 env.close(seconds(TOTAL_TIME_SLOT_SECS + 1));
-
3133 // clock is parent's based
-
3134 env.close();
-
3135 if (!features[fixAMMv1_1])
-
3136 BEAST_EXPECT(
-
3137 env.balance(carol, USD) ==
-
3138 STAmount(USD, UINT64_C(29'399'00572620545), -11));
-
3139 else
-
3140 BEAST_EXPECT(
-
3141 env.balance(carol, USD) ==
-
3142 STAmount(USD, UINT64_C(29'399'00572620544), -11));
-
3143 ammTokens = ammAlice.getLPTokensBalance();
-
3144 for (int i = 0; i < 10; ++i)
-
3145 {
-
3146 auto const tokens = ammAlice.deposit(carol, USD(100));
-
3147 ammAlice.withdraw(carol, tokens, USD(0));
-
3148 }
-
3149 // carol pays ~9.94USD in fees, which is ~10 times more in
-
3150 // trading fees vs discounted fee.
-
3151 if (!features[fixAMMv1_1])
-
3152 {
-
3153 BEAST_EXPECT(
-
3154 env.balance(carol, USD) ==
-
3155 STAmount(USD, UINT64_C(29'389'06197177128), -11));
-
3156 BEAST_EXPECT(ammAlice.expectBalances(
-
3157 XRPAmount{13'000'000'668},
-
3158 STAmount{USD, UINT64_C(13'123'98038490681), -11},
-
3159 ammTokens));
-
3160 }
-
3161 else
-
3162 {
-
3163 BEAST_EXPECT(
-
3164 env.balance(carol, USD) ==
-
3165 STAmount(USD, UINT64_C(29'389'06197177124), -11));
-
3166 BEAST_EXPECT(ammAlice.expectBalances(
-
3167 XRPAmount{13'000'000'668},
-
3168 STAmount{USD, UINT64_C(13'123'98038490689), -11},
-
3169 ammTokens));
-
3170 }
-
3171 env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110)));
-
3172 env.close();
-
3173 // carol pays ~1.008XRP in trading fee, which is
-
3174 // ~10 times more than the discounted fee.
-
3175 // 99.815876XRP is swapped in for 100USD
-
3176 if (!features[fixAMMv1_1])
-
3177 {
-
3178 BEAST_EXPECT(ammAlice.expectBalances(
-
3179 XRPAmount(13'100'824'790),
-
3180 STAmount{USD, UINT64_C(13'023'98038490681), -11},
-
3181 ammTokens));
-
3182 }
-
3183 else
-
3184 {
-
3185 BEAST_EXPECT(ammAlice.expectBalances(
-
3186 XRPAmount(13'100'824'790),
-
3187 STAmount{USD, UINT64_C(13'023'98038490689), -11},
-
3188 ammTokens));
-
3189 }
-
3190 },
-
3191 std::nullopt,
-
3192 1'000,
-
3193 std::nullopt,
-
3194 {features});
-
3195
-
3196 // Bid tiny amount
-
3197 testAMM(
-
3198 [&](AMM& ammAlice, Env& env) {
-
3199 // Bid a tiny amount
-
3200 auto const tiny =
-
3201 Number{STAmount::cMinValue, STAmount::cMinOffset};
-
3202 env(ammAlice.bid(
-
3203 {.account = alice, .bidMin = IOUAmount{tiny}}));
-
3204 // Auction slot purchase price is equal to the tiny amount
-
3205 // since the minSlotPrice is 0 with no trading fee.
-
3206 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
-
3207 // The purchase price is too small to affect the total tokens
-
3208 BEAST_EXPECT(ammAlice.expectBalances(
-
3209 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
3210 // Bid the tiny amount
-
3211 env(ammAlice.bid({
-
3212 .account = alice,
-
3213 .bidMin =
-
3214 IOUAmount{STAmount::cMinValue, STAmount::cMinOffset},
-
3215 }));
-
3216 // Pay slightly higher price
-
3217 BEAST_EXPECT(ammAlice.expectAuctionSlot(
-
3218 0, 0, IOUAmount{tiny * Number{105, -2}}));
-
3219 // The purchase price is still too small to affect the total
-
3220 // tokens
-
3221 BEAST_EXPECT(ammAlice.expectBalances(
-
3222 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
3223 },
-
3224 std::nullopt,
-
3225 0,
-
3226 std::nullopt,
-
3227 {features});
-
3228
-
3229 // Reset auth account
-
3230 testAMM(
-
3231 [&](AMM& ammAlice, Env& env) {
-
3232 env(ammAlice.bid({
-
3233 .account = alice,
-
3234 .bidMin = IOUAmount{100},
-
3235 .authAccounts = {carol},
-
3236 }));
-
3237 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
-
3238 env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}}));
-
3239 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
-
3240 Account bob("bob");
-
3241 Account dan("dan");
-
3242 fund(env, {bob, dan}, XRP(1'000));
-
3243 env(ammAlice.bid({
-
3244 .account = alice,
-
3245 .bidMin = IOUAmount{100},
-
3246 .authAccounts = {bob, dan},
-
3247 }));
-
3248 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
-
3249 },
-
3250 std::nullopt,
-
3251 0,
-
3252 std::nullopt,
-
3253 {features});
-
3254
-
3255 // Bid all tokens, still own the slot and trade at a discount
-
3256 {
-
3257 Env env(*this, features);
-
3258 fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
-
3259 AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
-
3260 auto const lpIssue = amm.lptIssue();
-
3261 env.trust(STAmount{lpIssue, 500}, alice);
-
3262 env.trust(STAmount{lpIssue, 50}, bob);
-
3263 env(pay(gw, alice, STAmount{lpIssue, 500}));
-
3264 env(pay(gw, bob, STAmount{lpIssue, 50}));
-
3265 // Alice doesn't have anymore lp tokens
-
3266 env(amm.bid({.account = alice, .bidMin = 500}));
-
3267 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{500}));
-
3268 BEAST_EXPECT(expectLine(env, alice, STAmount{lpIssue, 0}));
-
3269 // But trades with the discounted fee since she still owns the slot.
-
3270 // Alice pays 10011 drops in fees
-
3271 env(pay(alice, bob, USD(10)), path(~USD), sendmax(XRP(11)));
-
3272 BEAST_EXPECT(amm.expectBalances(
-
3273 XRPAmount{1'010'010'011},
-
3274 USD(1'000),
-
3275 IOUAmount{1'004'487'562112089, -9}));
-
3276 // Bob pays the full fee ~0.1USD
-
3277 env(pay(bob, alice, XRP(10)), path(~XRP), sendmax(USD(11)));
-
3278 if (!features[fixAMMv1_1])
-
3279 {
-
3280 BEAST_EXPECT(amm.expectBalances(
-
3281 XRPAmount{1'000'010'011},
-
3282 STAmount{USD, UINT64_C(1'010'10090898081), -11},
-
3283 IOUAmount{1'004'487'562112089, -9}));
-
3284 }
-
3285 else
-
3286 {
-
3287 BEAST_EXPECT(amm.expectBalances(
-
3288 XRPAmount{1'000'010'011},
-
3289 STAmount{USD, UINT64_C(1'010'100908980811), -12},
-
3290 IOUAmount{1'004'487'562112089, -9}));
-
3291 }
-
3292 }
-
3293
-
3294 // preflight tests
-
3295 {
-
3296 Env env(*this, features);
-
3297 auto const baseFee = env.current()->fees().base;
-
3298
-
3299 fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
-
3300 AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
-
3301 Json::Value tx = amm.bid({.account = alice, .bidMin = 500});
-
3302
-
3303 {
-
3304 auto jtx = env.jt(tx, seq(1), fee(baseFee));
-
3305 env.app().config().features.erase(featureAMM);
-
3306 PreflightContext pfctx(
-
3307 env.app(),
-
3308 *jtx.stx,
-
3309 env.current()->rules(),
-
3310 tapNONE,
-
3311 env.journal);
-
3312 auto pf = AMMBid::preflight(pfctx);
-
3313 BEAST_EXPECT(pf == temDISABLED);
-
3314 env.app().config().features.insert(featureAMM);
-
3315 }
-
3316
-
3317 {
-
3318 auto jtx = env.jt(tx, seq(1), fee(baseFee));
-
3319 jtx.jv["TxnSignature"] = "deadbeef";
-
3320 jtx.stx = env.ust(jtx);
-
3321 PreflightContext pfctx(
-
3322 env.app(),
-
3323 *jtx.stx,
-
3324 env.current()->rules(),
-
3325 tapNONE,
-
3326 env.journal);
-
3327 auto pf = AMMBid::preflight(pfctx);
-
3328 BEAST_EXPECT(pf != tesSUCCESS);
-
3329 }
-
3330
-
3331 {
-
3332 auto jtx = env.jt(tx, seq(1), fee(baseFee));
-
3333 jtx.jv["Asset2"]["currency"] = "XRP";
-
3334 jtx.jv["Asset2"].removeMember("issuer");
-
3335 jtx.stx = env.ust(jtx);
-
3336 PreflightContext pfctx(
-
3337 env.app(),
-
3338 *jtx.stx,
-
3339 env.current()->rules(),
-
3340 tapNONE,
-
3341 env.journal);
-
3342 auto pf = AMMBid::preflight(pfctx);
-
3343 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
-
3344 }
-
3345 }
-
3346 }
-
3347
-
3348 void
-
3349 testInvalidAMMPayment()
-
3350 {
-
3351 testcase("Invalid AMM Payment");
-
3352 using namespace jtx;
-
3353 using namespace std::chrono;
-
3354 using namespace std::literals::chrono_literals;
-
3355
-
3356 // Can't pay into AMM account.
-
3357 // Can't pay out since there is no keys
-
3358 for (auto const& acct : {gw, alice})
-
3359 {
-
3360 {
-
3361 Env env(*this);
-
3362 fund(env, gw, {alice, carol}, XRP(1'000), {USD(100)});
-
3363 // XRP balance is below reserve
-
3364 AMM ammAlice(env, acct, XRP(10), USD(10));
-
3365 // Pay below reserve
-
3366 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
-
3367 ter(tecNO_PERMISSION));
-
3368 // Pay above reserve
-
3369 env(pay(carol, ammAlice.ammAccount(), XRP(300)),
-
3370 ter(tecNO_PERMISSION));
-
3371 // Pay IOU
-
3372 env(pay(carol, ammAlice.ammAccount(), USD(10)),
-
3373 ter(tecNO_PERMISSION));
-
3374 }
-
3375 {
-
3376 Env env(*this);
-
3377 fund(env, gw, {alice, carol}, XRP(10'000'000), {USD(10'000)});
-
3378 // XRP balance is above reserve
-
3379 AMM ammAlice(env, acct, XRP(1'000'000), USD(100));
-
3380 // Pay below reserve
-
3381 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
-
3382 ter(tecNO_PERMISSION));
-
3383 // Pay above reserve
-
3384 env(pay(carol, ammAlice.ammAccount(), XRP(1'000'000)),
-
3385 ter(tecNO_PERMISSION));
-
3386 }
-
3387 }
-
3388
-
3389 // Can't pay into AMM with escrow.
-
3390 testAMM([&](AMM& ammAlice, Env& env) {
-
3391 auto const baseFee = env.current()->fees().base;
-
3392 env(escrow(carol, ammAlice.ammAccount(), XRP(1)),
-
3393 condition(cb1),
-
3394 finish_time(env.now() + 1s),
-
3395 cancel_time(env.now() + 2s),
-
3396 fee(baseFee * 150),
-
3397 ter(tecNO_PERMISSION));
-
3398 });
-
3399
-
3400 // Can't pay into AMM with paychan.
-
3401 testAMM([&](AMM& ammAlice, Env& env) {
-
3402 auto const pk = carol.pk();
-
3403 auto const settleDelay = 100s;
-
3404 NetClock::time_point const cancelAfter =
-
3405 env.current()->info().parentCloseTime + 200s;
-
3406 env(create(
-
3407 carol,
-
3408 ammAlice.ammAccount(),
-
3409 XRP(1'000),
-
3410 settleDelay,
-
3411 pk,
-
3412 cancelAfter),
-
3413 ter(tecNO_PERMISSION));
-
3414 });
-
3415
-
3416 // Can't pay into AMM with checks.
-
3417 testAMM([&](AMM& ammAlice, Env& env) {
-
3418 env(check::create(env.master.id(), ammAlice.ammAccount(), XRP(100)),
-
3419 ter(tecNO_PERMISSION));
-
3420 });
-
3421
-
3422 // Pay amounts close to one side of the pool
-
3423 testAMM(
-
3424 [&](AMM& ammAlice, Env& env) {
-
3425 // Can't consume whole pool
-
3426 env(pay(alice, carol, USD(100)),
-
3427 path(~USD),
-
3428 sendmax(XRP(1'000'000'000)),
-
3429 ter(tecPATH_PARTIAL));
-
3430 env(pay(alice, carol, XRP(100)),
-
3431 path(~XRP),
-
3432 sendmax(USD(1'000'000'000)),
-
3433 ter(tecPATH_PARTIAL));
-
3434 // Overflow
-
3435 env(pay(alice,
-
3436 carol,
-
3437 STAmount{USD, UINT64_C(99'999999999), -9}),
-
3438 path(~USD),
-
3439 sendmax(XRP(1'000'000'000)),
-
3440 ter(tecPATH_PARTIAL));
-
3441 env(pay(alice,
-
3442 carol,
-
3443 STAmount{USD, UINT64_C(999'99999999), -8}),
-
3444 path(~USD),
-
3445 sendmax(XRP(1'000'000'000)),
-
3446 ter(tecPATH_PARTIAL));
-
3447 env(pay(alice, carol, STAmount{xrpIssue(), 99'999'999}),
-
3448 path(~XRP),
-
3449 sendmax(USD(1'000'000'000)),
-
3450 ter(tecPATH_PARTIAL));
-
3451 // Sender doesn't have enough funds
-
3452 env(pay(alice, carol, USD(99.99)),
-
3453 path(~USD),
-
3454 sendmax(XRP(1'000'000'000)),
-
3455 ter(tecPATH_PARTIAL));
-
3456 env(pay(alice, carol, STAmount{xrpIssue(), 99'990'000}),
-
3457 path(~XRP),
-
3458 sendmax(USD(1'000'000'000)),
-
3459 ter(tecPATH_PARTIAL));
-
3460 },
-
3461 {{XRP(100), USD(100)}});
-
3462
-
3463 // Globally frozen
-
3464 testAMM([&](AMM& ammAlice, Env& env) {
-
3465 env(fset(gw, asfGlobalFreeze));
-
3466 env.close();
-
3467 env(pay(alice, carol, USD(1)),
-
3468 path(~USD),
-
3469 txflags(tfPartialPayment | tfNoRippleDirect),
-
3470 sendmax(XRP(10)),
-
3471 ter(tecPATH_DRY));
-
3472 env(pay(alice, carol, XRP(1)),
-
3473 path(~XRP),
-
3474 txflags(tfPartialPayment | tfNoRippleDirect),
-
3475 sendmax(USD(10)),
-
3476 ter(tecPATH_DRY));
-
3477 });
-
3478
-
3479 // Individually frozen AMM
-
3480 testAMM([&](AMM& ammAlice, Env& env) {
-
3481 env(trust(
-
3482 gw,
-
3483 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
-
3484 tfSetFreeze));
-
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 accounts
-
3499 testAMM([&](AMM& ammAlice, Env& env) {
-
3500 env(trust(gw, carol["USD"](0), tfSetFreeze));
-
3501 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
3502 env.close();
-
3503 env(pay(alice, carol, XRP(1)),
-
3504 path(~XRP),
-
3505 sendmax(USD(10)),
-
3506 txflags(tfNoRippleDirect | tfPartialPayment),
-
3507 ter(tecPATH_DRY));
-
3508 });
-
3509 }
-
3510
-
3511 void
-
3512 testBasicPaymentEngine(FeatureBitset features)
-
3513 {
-
3514 testcase("Basic Payment");
-
3515 using namespace jtx;
-
3516
-
3517 // Payment 100USD for 100XRP.
-
3518 // Force one path with tfNoRippleDirect.
-
3519 testAMM(
-
3520 [&](AMM& ammAlice, Env& env) {
-
3521 env.fund(jtx::XRP(30'000), bob);
-
3522 env.close();
-
3523 env(pay(bob, carol, USD(100)),
-
3524 path(~USD),
-
3525 sendmax(XRP(100)),
-
3526 txflags(tfNoRippleDirect));
-
3527 env.close();
-
3528 BEAST_EXPECT(ammAlice.expectBalances(
-
3529 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3530 // Initial balance 30,000 + 100
-
3531 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
3532 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
-
3533 BEAST_EXPECT(expectLedgerEntryRoot(
-
3534 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
-
3535 },
-
3536 {{XRP(10'000), USD(10'100)}},
-
3537 0,
-
3538 std::nullopt,
-
3539 {features});
-
3540
-
3541 // Payment 100USD for 100XRP, use default path.
-
3542 testAMM(
-
3543 [&](AMM& ammAlice, Env& env) {
-
3544 env.fund(jtx::XRP(30'000), bob);
-
3545 env.close();
-
3546 env(pay(bob, carol, USD(100)), sendmax(XRP(100)));
-
3547 env.close();
-
3548 BEAST_EXPECT(ammAlice.expectBalances(
-
3549 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3550 // Initial balance 30,000 + 100
-
3551 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
3552 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
-
3553 BEAST_EXPECT(expectLedgerEntryRoot(
-
3554 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
-
3555 },
-
3556 {{XRP(10'000), USD(10'100)}},
-
3557 0,
-
3558 std::nullopt,
-
3559 {features});
-
3560
-
3561 // This payment is identical to above. While it has
-
3562 // both default path and path, activeStrands has one path.
-
3563 testAMM(
-
3564 [&](AMM& ammAlice, Env& env) {
-
3565 env.fund(jtx::XRP(30'000), bob);
-
3566 env.close();
-
3567 env(pay(bob, carol, USD(100)), path(~USD), sendmax(XRP(100)));
-
3568 env.close();
-
3569 BEAST_EXPECT(ammAlice.expectBalances(
-
3570 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3571 // Initial balance 30,000 + 100
-
3572 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
3573 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
-
3574 BEAST_EXPECT(expectLedgerEntryRoot(
-
3575 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
-
3576 },
-
3577 {{XRP(10'000), USD(10'100)}},
-
3578 0,
-
3579 std::nullopt,
-
3580 {features});
-
3581
-
3582 // Payment with limitQuality set.
-
3583 testAMM(
-
3584 [&](AMM& ammAlice, Env& env) {
-
3585 env.fund(jtx::XRP(30'000), bob);
-
3586 env.close();
-
3587 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
-
3588 // would have been sent has it not been for limitQuality.
-
3589 env(pay(bob, carol, USD(100)),
-
3590 path(~USD),
-
3591 sendmax(XRP(100)),
-
3592 txflags(
-
3593 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
3594 env.close();
-
3595 BEAST_EXPECT(ammAlice.expectBalances(
-
3596 XRP(10'010), USD(10'000), ammAlice.tokens()));
-
3597 // Initial balance 30,000 + 10(limited by limitQuality)
-
3598 BEAST_EXPECT(expectLine(env, carol, USD(30'010)));
-
3599 // Initial balance 30,000 - 10(limited by limitQuality) - 10(tx
-
3600 // fee)
-
3601 BEAST_EXPECT(expectLedgerEntryRoot(
-
3602 env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
-
3603
-
3604 // Fails because of limitQuality. Would have sent
-
3605 // ~98.91USD/110XRP has it not been for limitQuality.
-
3606 env(pay(bob, carol, USD(100)),
-
3607 path(~USD),
-
3608 sendmax(XRP(100)),
-
3609 txflags(
-
3610 tfNoRippleDirect | tfPartialPayment | tfLimitQuality),
-
3611 ter(tecPATH_DRY));
-
3612 env.close();
-
3613 },
-
3614 {{XRP(10'000), USD(10'010)}},
-
3615 0,
-
3616 std::nullopt,
-
3617 {features});
-
3618
-
3619 // Payment with limitQuality and transfer fee set.
-
3620 testAMM(
-
3621 [&](AMM& ammAlice, Env& env) {
-
3622 env(rate(gw, 1.1));
-
3623 env.close();
-
3624 env.fund(jtx::XRP(30'000), bob);
-
3625 env.close();
-
3626 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
-
3627 // would have been sent has it not been for limitQuality and
-
3628 // the transfer fee.
-
3629 env(pay(bob, carol, USD(100)),
-
3630 path(~USD),
-
3631 sendmax(XRP(110)),
-
3632 txflags(
-
3633 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
3634 env.close();
-
3635 BEAST_EXPECT(ammAlice.expectBalances(
-
3636 XRP(10'010), USD(10'000), ammAlice.tokens()));
-
3637 // 10USD - 10% transfer fee
-
3638 BEAST_EXPECT(expectLine(
-
3639 env,
-
3640 carol,
-
3641 STAmount{USD, UINT64_C(30'009'09090909091), -11}));
-
3642 BEAST_EXPECT(expectLedgerEntryRoot(
-
3643 env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
-
3644 },
-
3645 {{XRP(10'000), USD(10'010)}},
-
3646 0,
-
3647 std::nullopt,
-
3648 {features});
-
3649
-
3650 // Fail when partial payment is not set.
-
3651 testAMM(
-
3652 [&](AMM& ammAlice, Env& env) {
-
3653 env.fund(jtx::XRP(30'000), bob);
-
3654 env.close();
-
3655 env(pay(bob, carol, USD(100)),
-
3656 path(~USD),
-
3657 sendmax(XRP(100)),
-
3658 txflags(tfNoRippleDirect),
-
3659 ter(tecPATH_PARTIAL));
-
3660 },
-
3661 {{XRP(10'000), USD(10'000)}},
-
3662 0,
-
3663 std::nullopt,
-
3664 {features});
-
3665
-
3666 // Non-default path (with AMM) has a better quality than default path.
-
3667 // The max possible liquidity is taken out of non-default
-
3668 // path ~29.9XRP/29.9EUR, ~29.9EUR/~29.99USD. The rest
-
3669 // is taken from the offer.
-
3670 {
-
3671 Env env(*this, features);
-
3672 fund(
-
3673 env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
-
3674 env.close();
-
3675 env.fund(XRP(1'000), bob);
-
3676 env.close();
-
3677 auto ammEUR_XRP = AMM(env, alice, XRP(10'000), EUR(10'000));
-
3678 auto ammUSD_EUR = AMM(env, alice, EUR(10'000), USD(10'000));
-
3679 env(offer(alice, XRP(101), USD(100)), txflags(tfPassive));
-
3680 env.close();
-
3681 env(pay(bob, carol, USD(100)),
-
3682 path(~EUR, ~USD),
-
3683 sendmax(XRP(102)),
-
3684 txflags(tfPartialPayment));
-
3685 env.close();
-
3686 BEAST_EXPECT(ammEUR_XRP.expectBalances(
-
3687 XRPAmount(10'030'082'730),
-
3688 STAmount(EUR, UINT64_C(9'970'007498125468), -12),
-
3689 ammEUR_XRP.tokens()));
-
3690 if (!features[fixAMMv1_1])
-
3691 {
-
3692 BEAST_EXPECT(ammUSD_EUR.expectBalances(
-
3693 STAmount(USD, UINT64_C(9'970'097277662122), -12),
-
3694 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
-
3695 ammUSD_EUR.tokens()));
-
3696
-
3697 // fixReducedOffersV2 changes the expected results slightly.
-
3698 Amounts const expectedAmounts =
-
3699 env.closed()->rules().enabled(fixReducedOffersV2)
-
3700 ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233787816), -14)}
-
3701 : Amounts{
-
3702 XRPAmount(30'201'749),
-
3703 STAmount(USD, UINT64_C(29'90272233787818), -14)};
-
3704
-
3705 BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}}));
-
3706 }
-
3707 else
-
3708 {
-
3709 BEAST_EXPECT(ammUSD_EUR.expectBalances(
-
3710 STAmount(USD, UINT64_C(9'970'097277662172), -12),
-
3711 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
-
3712 ammUSD_EUR.tokens()));
-
3713
-
3714 // fixReducedOffersV2 changes the expected results slightly.
-
3715 Amounts const expectedAmounts =
-
3716 env.closed()->rules().enabled(fixReducedOffersV2)
-
3717 ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233782839), -14)}
-
3718 : Amounts{
-
3719 XRPAmount(30'201'749),
-
3720 STAmount(USD, UINT64_C(29'90272233782840), -14)};
-
3721
-
3722 BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}}));
-
3723 }
-
3724 // Initial 30,000 + 100
-
3725 BEAST_EXPECT(expectLine(env, carol, STAmount{USD, 30'100}));
-
3726 // Initial 1,000 - 30082730(AMM pool) - 70798251(offer) - 10(tx fee)
-
3727 BEAST_EXPECT(expectLedgerEntryRoot(
-
3728 env,
-
3729 bob,
-
3730 XRP(1'000) - XRPAmount{30'082'730} - XRPAmount{70'798'251} -
-
3731 txfee(env, 1)));
-
3732 }
-
3733
-
3734 // Default path (with AMM) has a better quality than a non-default path.
-
3735 // The max possible liquidity is taken out of default
-
3736 // path ~49XRP/49USD. The rest is taken from the offer.
-
3737 testAMM(
-
3738 [&](AMM& ammAlice, Env& env) {
-
3739 env.fund(XRP(1'000), bob);
-
3740 env.close();
-
3741 env.trust(EUR(2'000), alice);
-
3742 env.close();
-
3743 env(pay(gw, alice, EUR(1'000)));
-
3744 env(offer(alice, XRP(101), EUR(100)), txflags(tfPassive));
-
3745 env.close();
-
3746 env(offer(alice, EUR(100), USD(100)), txflags(tfPassive));
-
3747 env.close();
-
3748 env(pay(bob, carol, USD(100)),
-
3749 path(~EUR, ~USD),
-
3750 sendmax(XRP(102)),
-
3751 txflags(tfPartialPayment));
-
3752 env.close();
-
3753 BEAST_EXPECT(ammAlice.expectBalances(
-
3754 XRPAmount(10'050'238'637),
-
3755 STAmount(USD, UINT64_C(9'950'01249687578), -11),
-
3756 ammAlice.tokens()));
-
3757 BEAST_EXPECT(expectOffers(
-
3758 env,
-
3759 alice,
-
3760 2,
-
3761 {{Amounts{
-
3762 XRPAmount(50'487'378),
-
3763 STAmount(EUR, UINT64_C(49'98750312422), -11)},
-
3764 Amounts{
-
3765 STAmount(EUR, UINT64_C(49'98750312422), -11),
-
3766 STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
-
3767 // Initial 30,000 + 99.99999999999
-
3768 BEAST_EXPECT(expectLine(
-
3769 env,
-
3770 carol,
-
3771 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
-
3772 // Initial 1,000 - 50238637(AMM pool) - 50512622(offer) - 10(tx
-
3773 // fee)
-
3774 BEAST_EXPECT(expectLedgerEntryRoot(
-
3775 env,
-
3776 bob,
-
3777 XRP(1'000) - XRPAmount{50'238'637} - XRPAmount{50'512'622} -
-
3778 txfee(env, 1)));
-
3779 },
-
3780 std::nullopt,
-
3781 0,
-
3782 std::nullopt,
-
3783 {features});
-
3784
-
3785 // Default path with AMM and Order Book offer. AMM is consumed first,
-
3786 // remaining amount is consumed by the offer.
-
3787 testAMM(
-
3788 [&](AMM& ammAlice, Env& env) {
-
3789 fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
-
3790 env.close();
-
3791 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
-
3792 env.close();
-
3793 env(pay(alice, carol, USD(200)),
-
3794 sendmax(XRP(200)),
-
3795 txflags(tfPartialPayment));
-
3796 env.close();
-
3797 if (!features[fixAMMv1_1])
-
3798 {
-
3799 BEAST_EXPECT(ammAlice.expectBalances(
-
3800 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3801 // Initial 30,000 + 200
-
3802 BEAST_EXPECT(expectLine(env, carol, USD(30'200)));
-
3803 }
-
3804 else
-
3805 {
-
3806 BEAST_EXPECT(ammAlice.expectBalances(
-
3807 XRP(10'100),
-
3808 STAmount(USD, UINT64_C(10'000'00000000001), -11),
-
3809 ammAlice.tokens()));
-
3810 BEAST_EXPECT(expectLine(
-
3811 env,
-
3812 carol,
-
3813 STAmount(USD, UINT64_C(30'199'99999999999), -11)));
-
3814 }
-
3815 // Initial 30,000 - 10000(AMM pool LP) - 100(AMM offer) -
-
3816 // - 100(offer) - 10(tx fee) - one reserve
-
3817 BEAST_EXPECT(expectLedgerEntryRoot(
-
3818 env,
-
3819 alice,
-
3820 XRP(30'000) - XRP(10'000) - XRP(100) - XRP(100) -
-
3821 ammCrtFee(env) - txfee(env, 1)));
-
3822 BEAST_EXPECT(expectOffers(env, bob, 0));
-
3823 },
-
3824 {{XRP(10'000), USD(10'100)}},
-
3825 0,
-
3826 std::nullopt,
-
3827 {features});
-
3828
-
3829 // Default path with AMM and Order Book offer.
-
3830 // Order Book offer is consumed first.
-
3831 // Remaining amount is consumed by AMM.
-
3832 {
-
3833 Env env(*this, features);
-
3834 fund(env, gw, {alice, bob, carol}, XRP(20'000), {USD(2'000)});
-
3835 env.close();
-
3836 env(offer(bob, XRP(50), USD(150)), txflags(tfPassive));
-
3837 env.close();
-
3838 AMM ammAlice(env, alice, XRP(1'000), USD(1'050));
-
3839 env(pay(alice, carol, USD(200)),
-
3840 sendmax(XRP(200)),
-
3841 txflags(tfPartialPayment));
-
3842 env.close();
-
3843 BEAST_EXPECT(ammAlice.expectBalances(
-
3844 XRP(1'050), USD(1'000), ammAlice.tokens()));
-
3845 BEAST_EXPECT(expectLine(env, carol, USD(2'200)));
-
3846 BEAST_EXPECT(expectOffers(env, bob, 0));
-
3847 }
-
3848
-
3849 // Offer crossing XRP/IOU
-
3850 testAMM(
-
3851 [&](AMM& ammAlice, Env& env) {
-
3852 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
-
3853 env.close();
-
3854 env(offer(bob, USD(100), XRP(100)));
-
3855 env.close();
-
3856 BEAST_EXPECT(ammAlice.expectBalances(
-
3857 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3858 // Initial 1,000 + 100
-
3859 BEAST_EXPECT(expectLine(env, bob, USD(1'100)));
-
3860 // Initial 30,000 - 100(offer) - 10(tx fee)
-
3861 BEAST_EXPECT(expectLedgerEntryRoot(
-
3862 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
-
3863 BEAST_EXPECT(expectOffers(env, bob, 0));
-
3864 },
-
3865 {{XRP(10'000), USD(10'100)}},
-
3866 0,
-
3867 std::nullopt,
-
3868 {features});
-
3869
-
3870 // Offer crossing IOU/IOU and transfer rate
-
3871 // Single path AMM offer
-
3872 testAMM(
-
3873 [&](AMM& ammAlice, Env& env) {
-
3874 env(rate(gw, 1.25));
-
3875 env.close();
-
3876 // This offer succeeds to cross pre- and post-amendment
-
3877 // because the strand's out amount is small enough to match
-
3878 // limitQuality value and limitOut() function in StrandFlow
-
3879 // doesn't require an adjustment to out value.
-
3880 env(offer(carol, EUR(100), GBP(100)));
-
3881 env.close();
-
3882 // No transfer fee
-
3883 BEAST_EXPECT(ammAlice.expectBalances(
-
3884 GBP(1'100), EUR(1'000), ammAlice.tokens()));
-
3885 // Initial 30,000 - 100(offer) - 25% transfer fee
-
3886 BEAST_EXPECT(expectLine(env, carol, GBP(29'875)));
-
3887 // Initial 30,000 + 100(offer)
-
3888 BEAST_EXPECT(expectLine(env, carol, EUR(30'100)));
-
3889 BEAST_EXPECT(expectOffers(env, bob, 0));
-
3890 },
-
3891 {{GBP(1'000), EUR(1'100)}},
-
3892 0,
-
3893 std::nullopt,
-
3894 {features});
-
3895 // Single-path AMM offer
-
3896 testAMM(
-
3897 [&](AMM& amm, Env& env) {
-
3898 env(rate(gw, 1.001));
-
3899 env.close();
-
3900 env(offer(carol, XRP(100), USD(55)));
-
3901 env.close();
-
3902 if (!features[fixAMMv1_1])
-
3903 {
-
3904 // Pre-amendment the transfer fee is not taken into
-
3905 // account when calculating the limit out based on
-
3906 // limitQuality. Carol pays 0.1% on the takerGets, which
-
3907 // lowers the overall quality. AMM offer is generated based
-
3908 // on higher limit out, which generates a larger offer
-
3909 // with lower quality. Consequently, the offer fails
-
3910 // to cross.
-
3911 BEAST_EXPECT(
-
3912 amm.expectBalances(XRP(1'000), USD(500), amm.tokens()));
-
3913 BEAST_EXPECT(expectOffers(
-
3914 env, carol, 1, {{Amounts{XRP(100), USD(55)}}}));
-
3915 }
-
3916 else
-
3917 {
-
3918 // Post-amendment the transfer fee is taken into account
-
3919 // when calculating the limit out based on limitQuality.
-
3920 // This increases the limitQuality and decreases
-
3921 // the limit out. Consequently, AMM offer size is decreased,
-
3922 // and the quality is increased, matching the overall
-
3923 // quality.
-
3924 // AMM offer ~50USD/91XRP
-
3925 BEAST_EXPECT(amm.expectBalances(
-
3926 XRPAmount(909'090'909),
-
3927 STAmount{USD, UINT64_C(550'000000055), -9},
-
3928 amm.tokens()));
-
3929 // Offer ~91XRP/49.99USD
-
3930 BEAST_EXPECT(expectOffers(
-
3931 env,
-
3932 carol,
-
3933 1,
-
3934 {{Amounts{
-
3935 XRPAmount{9'090'909},
-
3936 STAmount{USD, 4'99999995, -8}}}}));
-
3937 // Carol pays 0.1% fee on ~50USD =~ 0.05USD
-
3938 BEAST_EXPECT(
-
3939 env.balance(carol, USD) ==
-
3940 STAmount(USD, UINT64_C(29'949'94999999494), -11));
-
3941 }
-
3942 },
-
3943 {{XRP(1'000), USD(500)}},
-
3944 0,
-
3945 std::nullopt,
-
3946 {features});
-
3947 testAMM(
-
3948 [&](AMM& amm, Env& env) {
-
3949 env(rate(gw, 1.001));
-
3950 env.close();
-
3951 env(offer(carol, XRP(10), USD(5.5)));
-
3952 env.close();
-
3953 if (!features[fixAMMv1_1])
-
3954 {
-
3955 BEAST_EXPECT(amm.expectBalances(
-
3956 XRP(990),
-
3957 STAmount{USD, UINT64_C(505'050505050505), -12},
-
3958 amm.tokens()));
-
3959 BEAST_EXPECT(expectOffers(env, carol, 0));
-
3960 }
-
3961 else
-
3962 {
-
3963 BEAST_EXPECT(amm.expectBalances(
-
3964 XRP(990),
-
3965 STAmount{USD, UINT64_C(505'0505050505051), -13},
-
3966 amm.tokens()));
-
3967 BEAST_EXPECT(expectOffers(env, carol, 0));
-
3968 }
-
3969 },
-
3970 {{XRP(1'000), USD(500)}},
-
3971 0,
-
3972 std::nullopt,
-
3973 {features});
-
3974 // Multi-path AMM offer
-
3975 testAMM(
-
3976 [&](AMM& ammAlice, Env& env) {
-
3977 Account const ed("ed");
-
3978 fund(
-
3979 env,
-
3980 gw,
-
3981 {bob, ed},
-
3982 XRP(30'000),
-
3983 {GBP(2'000), EUR(2'000)},
-
3984 Fund::Acct);
-
3985 env(rate(gw, 1.25));
-
3986 env.close();
-
3987 // The auto-bridge is worse quality than AMM, is not consumed
-
3988 // first and initially forces multi-path AMM offer generation.
-
3989 // Multi-path AMM offers are consumed until their quality
-
3990 // is less than the auto-bridge offers quality. Auto-bridge
-
3991 // offers are consumed afterward. Then the behavior is
-
3992 // different pre-amendment and post-amendment.
-
3993 env(offer(bob, GBP(10), XRP(10)), txflags(tfPassive));
-
3994 env(offer(ed, XRP(10), EUR(10)), txflags(tfPassive));
-
3995 env.close();
-
3996 env(offer(carol, EUR(100), GBP(100)));
-
3997 env.close();
-
3998 if (!features[fixAMMv1_1])
-
3999 {
-
4000 // After the auto-bridge offers are consumed, single path
-
4001 // AMM offer is generated with the limit out not taking
-
4002 // into consideration the transfer fee. This results
-
4003 // in an overall lower quality offer than the limit quality
-
4004 // and the single path AMM offer fails to consume.
-
4005 // Total consumed ~37.06GBP/39.32EUR
-
4006 BEAST_EXPECT(ammAlice.expectBalances(
-
4007 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
-
4008 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
-
4009 ammAlice.tokens()));
-
4010 // Consumed offer ~49.32EUR/49.32GBP
-
4011 BEAST_EXPECT(expectOffers(
-
4012 env,
-
4013 carol,
-
4014 1,
-
4015 {Amounts{
-
4016 STAmount{EUR, UINT64_C(50'684828792831), -12},
-
4017 STAmount{GBP, UINT64_C(50'684828792831), -12}}}));
-
4018 BEAST_EXPECT(expectOffers(env, bob, 0));
-
4019 BEAST_EXPECT(expectOffers(env, ed, 0));
-
4020
-
4021 // Initial 30,000 - ~47.06(offers = 37.06(AMM) + 10(LOB))
-
4022 // * 1.25
-
4023 // = 58.825 = ~29941.17
-
4024 // carol bought ~72.93EUR at the cost of ~70.68GBP
-
4025 // the offer is partially consumed
-
4026 BEAST_EXPECT(expectLine(
-
4027 env,
-
4028 carol,
-
4029 STAmount{GBP, UINT64_C(29'941'16770347333), -11}));
-
4030 // Initial 30,000 + ~49.3(offers = 39.3(AMM) + 10(LOB))
-
4031 BEAST_EXPECT(expectLine(
-
4032 env,
-
4033 carol,
-
4034 STAmount{EUR, UINT64_C(30'049'31517120716), -11}));
-
4035 }
-
4036 else
-
4037 {
-
4038 // After the auto-bridge offers are consumed, single path
-
4039 // AMM offer is generated with the limit out taking
-
4040 // into consideration the transfer fee. This results
-
4041 // in an overall quality offer matching the limit quality
-
4042 // and the single path AMM offer is consumed. More
-
4043 // liquidity is consumed overall in post-amendment.
-
4044 // Total consumed ~60.68GBP/62.93EUR
-
4045 BEAST_EXPECT(ammAlice.expectBalances(
-
4046 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
-
4047 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
-
4048 ammAlice.tokens()));
-
4049 // Consumed offer ~72.93EUR/72.93GBP
-
4050 BEAST_EXPECT(expectOffers(
-
4051 env,
-
4052 carol,
-
4053 1,
-
4054 {Amounts{
-
4055 STAmount{EUR, UINT64_C(27'06583722134028), -14},
-
4056 STAmount{GBP, UINT64_C(27'06583722134028), -14}}}));
-
4057 BEAST_EXPECT(expectOffers(env, bob, 0));
-
4058 BEAST_EXPECT(expectOffers(env, ed, 0));
-
4059
-
4060 // Initial 30,000 - ~70.68(offers = 60.68(AMM) + 10(LOB))
-
4061 // * 1.25
-
4062 // = 88.35 = ~29911.64
-
4063 // carol bought ~72.93EUR at the cost of ~70.68GBP
-
4064 // the offer is partially consumed
-
4065 BEAST_EXPECT(expectLine(
-
4066 env,
-
4067 carol,
-
4068 STAmount{GBP, UINT64_C(29'911'64396400896), -11}));
-
4069 // Initial 30,000 + ~72.93(offers = 62.93(AMM) + 10(LOB))
-
4070 BEAST_EXPECT(expectLine(
-
4071 env,
-
4072 carol,
-
4073 STAmount{EUR, UINT64_C(30'072'93416277865), -11}));
-
4074 }
-
4075 // Initial 2000 + 10 = 2010
-
4076 BEAST_EXPECT(expectLine(env, bob, GBP(2'010)));
-
4077 // Initial 2000 - 10 * 1.25 = 1987.5
-
4078 BEAST_EXPECT(expectLine(env, ed, EUR(1'987.5)));
-
4079 },
-
4080 {{GBP(1'000), EUR(1'100)}},
-
4081 0,
-
4082 std::nullopt,
-
4083 {features});
-
4084
-
4085 // Payment and transfer fee
-
4086 // Scenario:
-
4087 // Bob sends 125GBP to pay 80EUR to Carol
-
4088 // Payment execution:
-
4089 // bob's 125GBP/1.25 = 100GBP
-
4090 // 100GBP/100EUR AMM offer
-
4091 // 100EUR/1.25 = 80EUR paid to carol
-
4092 testAMM(
-
4093 [&](AMM& ammAlice, Env& env) {
-
4094 fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
-
4095 env(rate(gw, 1.25));
-
4096 env.close();
-
4097 env(pay(bob, carol, EUR(100)),
-
4098 path(~EUR),
-
4099 sendmax(GBP(125)),
-
4100 txflags(tfPartialPayment));
-
4101 env.close();
-
4102 BEAST_EXPECT(ammAlice.expectBalances(
-
4103 GBP(1'100), EUR(1'000), ammAlice.tokens()));
-
4104 BEAST_EXPECT(expectLine(env, bob, GBP(75)));
-
4105 BEAST_EXPECT(expectLine(env, carol, EUR(30'080)));
-
4106 },
-
4107 {{GBP(1'000), EUR(1'100)}},
-
4108 0,
-
4109 std::nullopt,
-
4110 {features});
-
4111
-
4112 // Payment and transfer fee, multiple steps
-
4113 // Scenario:
-
4114 // Dan's offer 200CAN/200GBP
-
4115 // AMM 1000GBP/10125EUR
-
4116 // Ed's offer 200EUR/200USD
-
4117 // Bob sends 195.3125CAN to pay 100USD to Carol
-
4118 // Payment execution:
-
4119 // bob's 195.3125CAN/1.25 = 156.25CAN -> dan's offer
-
4120 // 156.25CAN/156.25GBP 156.25GBP/1.25 = 125GBP -> AMM's offer
-
4121 // 125GBP/125EUR 125EUR/1.25 = 100EUR -> ed's offer
-
4122 // 100EUR/100USD 100USD/1.25 = 80USD paid to carol
-
4123 testAMM(
-
4124 [&](AMM& ammAlice, Env& env) {
-
4125 Account const dan("dan");
-
4126 Account const ed("ed");
-
4127 auto const CAN = gw["CAN"];
-
4128 fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
-
4129 fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
-
4130 fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
-
4131 env(trust(carol, USD(100)));
-
4132 env(rate(gw, 1.25));
-
4133 env.close();
-
4134 env(offer(dan, CAN(200), GBP(200)));
-
4135 env(offer(ed, EUR(200), USD(200)));
-
4136 env.close();
-
4137 env(pay(bob, carol, USD(100)),
-
4138 path(~GBP, ~EUR, ~USD),
-
4139 sendmax(CAN(195.3125)),
-
4140 txflags(tfPartialPayment));
-
4141 env.close();
-
4142 BEAST_EXPECT(expectLine(env, bob, CAN(0)));
-
4143 BEAST_EXPECT(expectLine(env, dan, CAN(356.25), GBP(43.75)));
-
4144 BEAST_EXPECT(ammAlice.expectBalances(
-
4145 GBP(10'125), EUR(10'000), ammAlice.tokens()));
-
4146 BEAST_EXPECT(expectLine(env, ed, EUR(300), USD(100)));
-
4147 BEAST_EXPECT(expectLine(env, carol, USD(80)));
-
4148 },
-
4149 {{GBP(10'000), EUR(10'125)}},
-
4150 0,
-
4151 std::nullopt,
-
4152 {features});
-
4153
-
4154 // Pay amounts close to one side of the pool
-
4155 testAMM(
-
4156 [&](AMM& ammAlice, Env& env) {
-
4157 env(pay(alice, carol, USD(99.99)),
-
4158 path(~USD),
-
4159 sendmax(XRP(1)),
-
4160 txflags(tfPartialPayment),
-
4161 ter(tesSUCCESS));
-
4162 env(pay(alice, carol, USD(100)),
-
4163 path(~USD),
-
4164 sendmax(XRP(1)),
-
4165 txflags(tfPartialPayment),
-
4166 ter(tesSUCCESS));
-
4167 env(pay(alice, carol, XRP(100)),
-
4168 path(~XRP),
-
4169 sendmax(USD(1)),
-
4170 txflags(tfPartialPayment),
-
4171 ter(tesSUCCESS));
-
4172 env(pay(alice, carol, STAmount{xrpIssue(), 99'999'900}),
-
4173 path(~XRP),
-
4174 sendmax(USD(1)),
-
4175 txflags(tfPartialPayment),
-
4176 ter(tesSUCCESS));
-
4177 },
-
4178 {{XRP(100), USD(100)}},
-
4179 0,
-
4180 std::nullopt,
-
4181 {features});
-
4182
-
4183 // Multiple paths/steps
-
4184 {
-
4185 Env env(*this, features);
-
4186 auto const ETH = gw["ETH"];
-
4187 fund(
-
4188 env,
-
4189 gw,
-
4190 {alice},
-
4191 XRP(100'000),
-
4192 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
-
4193 fund(env, gw, {carol, bob}, XRP(1'000), {USD(200)}, Fund::Acct);
-
4194 AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
-
4195 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
-
4196 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
-
4197 AMM xrp_usd(env, alice, XRP(10'150), USD(10'200));
-
4198 AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
-
4199 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
-
4200 AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
-
4201 env(pay(bob, carol, USD(100)),
-
4202 path(~EUR, ~BTC, ~USD),
-
4203 path(~USD),
-
4204 path(~ETH, ~EUR, ~USD),
-
4205 sendmax(XRP(200)));
-
4206 if (!features[fixAMMv1_1])
-
4207 {
-
4208 // XRP-ETH-EUR-USD
-
4209 // This path provides ~26.06USD/26.2XRP
-
4210 BEAST_EXPECT(xrp_eth.expectBalances(
-
4211 XRPAmount(10'026'208'900),
-
4212 STAmount{ETH, UINT64_C(10'073'65779244494), -11},
-
4213 xrp_eth.tokens()));
-
4214 BEAST_EXPECT(eth_eur.expectBalances(
-
4215 STAmount{ETH, UINT64_C(10'926'34220755506), -11},
-
4216 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
-
4217 eth_eur.tokens()));
-
4218 BEAST_EXPECT(eur_usd.expectBalances(
-
4219 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
-
4220 STAmount{USD, UINT64_C(9'973'93151712086), -11},
-
4221 eur_usd.tokens()));
-
4222 // XRP-USD path
-
4223 // This path provides ~73.9USD/74.1XRP
-
4224 BEAST_EXPECT(xrp_usd.expectBalances(
-
4225 XRPAmount(10'224'106'246),
-
4226 STAmount{USD, UINT64_C(10'126'06848287914), -11},
-
4227 xrp_usd.tokens()));
-
4228 }
-
4229 else
-
4230 {
-
4231 BEAST_EXPECT(xrp_eth.expectBalances(
-
4232 XRPAmount(10'026'208'900),
-
4233 STAmount{ETH, UINT64_C(10'073'65779244461), -11},
-
4234 xrp_eth.tokens()));
-
4235 BEAST_EXPECT(eth_eur.expectBalances(
-
4236 STAmount{ETH, UINT64_C(10'926'34220755539), -11},
-
4237 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
-
4238 eth_eur.tokens()));
-
4239 BEAST_EXPECT(eur_usd.expectBalances(
-
4240 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
-
4241 STAmount{USD, UINT64_C(9'973'93151712057), -11},
-
4242 eur_usd.tokens()));
-
4243 // XRP-USD path
-
4244 // This path provides ~73.9USD/74.1XRP
-
4245 BEAST_EXPECT(xrp_usd.expectBalances(
-
4246 XRPAmount(10'224'106'246),
-
4247 STAmount{USD, UINT64_C(10'126'06848287943), -11},
-
4248 xrp_usd.tokens()));
-
4249 }
-
4250
-
4251 // XRP-EUR-BTC-USD
-
4252 // This path doesn't provide any liquidity due to how
-
4253 // offers are generated in multi-path. Analytical solution
-
4254 // shows a different distribution:
-
4255 // XRP-EUR-BTC-USD 11.6USD/11.64XRP, XRP-USD 60.7USD/60.8XRP,
-
4256 // XRP-ETH-EUR-USD 27.6USD/27.6XRP
-
4257 BEAST_EXPECT(xrp_eur.expectBalances(
-
4258 XRP(10'100), EUR(10'000), xrp_eur.tokens()));
-
4259 BEAST_EXPECT(eur_btc.expectBalances(
-
4260 EUR(10'000), BTC(10'200), eur_btc.tokens()));
-
4261 BEAST_EXPECT(btc_usd.expectBalances(
-
4262 BTC(10'100), USD(10'000), btc_usd.tokens()));
-
4263
-
4264 BEAST_EXPECT(expectLine(env, carol, USD(300)));
-
4265 }
-
4266
-
4267 // Dependent AMM
-
4268 {
-
4269 Env env(*this, features);
-
4270 auto const ETH = gw["ETH"];
-
4271 fund(
-
4272 env,
-
4273 gw,
-
4274 {alice},
-
4275 XRP(40'000),
-
4276 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
-
4277 fund(env, gw, {carol, bob}, XRP(1000), {USD(200)}, Fund::Acct);
-
4278 AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
-
4279 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
-
4280 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
-
4281 AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
-
4282 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
-
4283 env(pay(bob, carol, USD(100)),
-
4284 path(~EUR, ~BTC, ~USD),
-
4285 path(~ETH, ~EUR, ~BTC, ~USD),
-
4286 sendmax(XRP(200)));
-
4287 if (!features[fixAMMv1_1])
-
4288 {
-
4289 // XRP-EUR-BTC-USD path provides ~17.8USD/~18.7XRP
-
4290 // XRP-ETH-EUR-BTC-USD path provides ~82.2USD/82.4XRP
-
4291 BEAST_EXPECT(xrp_eur.expectBalances(
-
4292 XRPAmount(10'118'738'472),
-
4293 STAmount{EUR, UINT64_C(9'981'544436337968), -12},
-
4294 xrp_eur.tokens()));
-
4295 BEAST_EXPECT(eur_btc.expectBalances(
-
4296 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
-
4297 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
-
4298 eur_btc.tokens()));
-
4299 BEAST_EXPECT(btc_usd.expectBalances(
-
4300 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
-
4301 USD(9'900),
-
4302 btc_usd.tokens()));
-
4303 BEAST_EXPECT(xrp_eth.expectBalances(
-
4304 XRPAmount(10'082'446'397),
-
4305 STAmount{ETH, UINT64_C(10'017'41072778012), -11},
-
4306 xrp_eth.tokens()));
-
4307 BEAST_EXPECT(eth_eur.expectBalances(
-
4308 STAmount{ETH, UINT64_C(10'982'58927221988), -11},
-
4309 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
-
4310 eth_eur.tokens()));
-
4311 }
-
4312 else
-
4313 {
-
4314 BEAST_EXPECT(xrp_eur.expectBalances(
-
4315 XRPAmount(10'118'738'472),
-
4316 STAmount{EUR, UINT64_C(9'981'544436337923), -12},
-
4317 xrp_eur.tokens()));
-
4318 BEAST_EXPECT(eur_btc.expectBalances(
-
4319 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
-
4320 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
-
4321 eur_btc.tokens()));
-
4322 BEAST_EXPECT(btc_usd.expectBalances(
-
4323 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
-
4324 USD(9'900),
-
4325 btc_usd.tokens()));
-
4326 BEAST_EXPECT(xrp_eth.expectBalances(
-
4327 XRPAmount(10'082'446'397),
-
4328 STAmount{ETH, UINT64_C(10'017'41072777996), -11},
-
4329 xrp_eth.tokens()));
-
4330 BEAST_EXPECT(eth_eur.expectBalances(
-
4331 STAmount{ETH, UINT64_C(10'982'58927222004), -11},
-
4332 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
-
4333 eth_eur.tokens()));
-
4334 }
-
4335 BEAST_EXPECT(expectLine(env, carol, USD(300)));
-
4336 }
-
4337
-
4338 // AMM offers limit
-
4339 // Consuming 30 CLOB offers, results in hitting 30 AMM offers limit.
-
4340 testAMM(
-
4341 [&](AMM& ammAlice, Env& env) {
-
4342 env.fund(XRP(1'000), bob);
-
4343 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
-
4344 env(trust(alice, EUR(200)));
-
4345 for (int i = 0; i < 30; ++i)
-
4346 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
-
4347 // This is worse quality offer than 30 offers above.
-
4348 // It will not be consumed because of AMM offers limit.
-
4349 env(offer(alice, EUR(140), XRP(100)));
-
4350 env(pay(bob, carol, USD(100)),
-
4351 path(~XRP, ~USD),
-
4352 sendmax(EUR(400)),
-
4353 txflags(tfPartialPayment | tfNoRippleDirect));
-
4354 if (!features[fixAMMv1_1])
-
4355 {
-
4356 // Carol gets ~29.91USD because of the AMM offers limit
-
4357 BEAST_EXPECT(ammAlice.expectBalances(
-
4358 XRP(10'030),
-
4359 STAmount{USD, UINT64_C(9'970'089730807577), -12},
-
4360 ammAlice.tokens()));
-
4361 BEAST_EXPECT(expectLine(
-
4362 env,
-
4363 carol,
-
4364 STAmount{USD, UINT64_C(30'029'91026919241), -11}));
-
4365 }
-
4366 else
-
4367 {
-
4368 BEAST_EXPECT(ammAlice.expectBalances(
-
4369 XRP(10'030),
-
4370 STAmount{USD, UINT64_C(9'970'089730807827), -12},
-
4371 ammAlice.tokens()));
-
4372 BEAST_EXPECT(expectLine(
-
4373 env,
-
4374 carol,
-
4375 STAmount{USD, UINT64_C(30'029'91026919217), -11}));
-
4376 }
-
4377 BEAST_EXPECT(
-
4378 expectOffers(env, alice, 1, {{{EUR(140), XRP(100)}}}));
-
4379 },
-
4380 std::nullopt,
-
4381 0,
-
4382 std::nullopt,
-
4383 {features});
-
4384 // This payment is fulfilled
-
4385 testAMM(
-
4386 [&](AMM& ammAlice, Env& env) {
-
4387 env.fund(XRP(1'000), bob);
-
4388 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
-
4389 env(trust(alice, EUR(200)));
-
4390 for (int i = 0; i < 29; ++i)
-
4391 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
-
4392 // This is worse quality offer than 30 offers above.
-
4393 // It will not be consumed because of AMM offers limit.
-
4394 env(offer(alice, EUR(140), XRP(100)));
-
4395 env(pay(bob, carol, USD(100)),
-
4396 path(~XRP, ~USD),
-
4397 sendmax(EUR(400)),
-
4398 txflags(tfPartialPayment | tfNoRippleDirect));
-
4399 BEAST_EXPECT(ammAlice.expectBalances(
-
4400 XRPAmount{10'101'010'102}, USD(9'900), ammAlice.tokens()));
-
4401 if (!features[fixAMMv1_1])
-
4402 {
-
4403 // Carol gets ~100USD
-
4404 BEAST_EXPECT(expectLine(
-
4405 env,
-
4406 carol,
-
4407 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
-
4408 }
-
4409 else
-
4410 {
-
4411 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
4412 }
-
4413 BEAST_EXPECT(expectOffers(
-
4414 env,
-
4415 alice,
-
4416 1,
-
4417 {{{STAmount{EUR, UINT64_C(39'1858572), -7},
-
4418 XRPAmount{27'989'898}}}}));
-
4419 },
-
4420 std::nullopt,
-
4421 0,
-
4422 std::nullopt,
-
4423 {features});
-
4424
-
4425 // Offer crossing with AMM and another offer. AMM has a better
-
4426 // quality and is consumed first.
-
4427 {
-
4428 Env env(*this, features);
-
4429 fund(env, gw, {alice, carol, bob}, XRP(30'000), {USD(30'000)});
-
4430 env(offer(bob, XRP(100), USD(100.001)));
-
4431 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
-
4432 env(offer(carol, USD(100), XRP(100)));
-
4433 if (!features[fixAMMv1_1])
-
4434 {
-
4435 BEAST_EXPECT(ammAlice.expectBalances(
-
4436 XRPAmount{10'049'825'373},
-
4437 STAmount{USD, UINT64_C(10'049'92586949302), -11},
-
4438 ammAlice.tokens()));
-
4439 BEAST_EXPECT(expectOffers(
-
4440 env,
-
4441 bob,
-
4442 1,
-
4443 {{{XRPAmount{50'074'629},
-
4444 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
-
4445 }
-
4446 else
-
4447 {
-
4448 BEAST_EXPECT(ammAlice.expectBalances(
-
4449 XRPAmount{10'049'825'372},
-
4450 STAmount{USD, UINT64_C(10'049'92587049303), -11},
-
4451 ammAlice.tokens()));
-
4452 BEAST_EXPECT(expectOffers(
-
4453 env,
-
4454 bob,
-
4455 1,
-
4456 {{{XRPAmount{50'074'628},
-
4457 STAmount{USD, UINT64_C(50'07512950697), -11}}}}));
-
4458 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
4459 }
-
4460 }
-
4461
-
4462 // Individually frozen account
-
4463 testAMM(
-
4464 [&](AMM& ammAlice, Env& env) {
-
4465 env(trust(gw, carol["USD"](0), tfSetFreeze));
-
4466 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
4467 env.close();
-
4468 env(pay(alice, carol, USD(1)),
-
4469 path(~USD),
-
4470 sendmax(XRP(10)),
-
4471 txflags(tfNoRippleDirect | tfPartialPayment),
-
4472 ter(tesSUCCESS));
-
4473 },
-
4474 std::nullopt,
-
4475 0,
-
4476 std::nullopt,
-
4477 {features});
-
4478 }
-
4479
-
4480 void
-
4481 testAMMTokens()
-
4482 {
-
4483 testcase("AMM Tokens");
-
4484 using namespace jtx;
-
4485
-
4486 // Offer crossing with AMM LPTokens and XRP.
-
4487 testAMM([&](AMM& ammAlice, Env& env) {
-
4488 auto const baseFee = env.current()->fees().base.drops();
-
4489 auto const token1 = ammAlice.lptIssue();
-
4490 auto priceXRP = withdrawByTokens(
-
4491 STAmount{XRPAmount{10'000'000'000}},
-
4492 STAmount{token1, 10'000'000},
-
4493 STAmount{token1, 5'000'000},
-
4494 0);
-
4495 // Carol places an order to buy LPTokens
-
4496 env(offer(carol, STAmount{token1, 5'000'000}, priceXRP));
-
4497 // Alice places an order to sell LPTokens
-
4498 env(offer(alice, priceXRP, STAmount{token1, 5'000'000}));
-
4499 // Pool's LPTokens balance doesn't change
-
4500 BEAST_EXPECT(ammAlice.expectBalances(
-
4501 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
-
4502 // Carol is Liquidity Provider
-
4503 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{5'000'000}));
-
4504 BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
-
4505 // Carol votes
-
4506 ammAlice.vote(carol, 1'000);
-
4507 BEAST_EXPECT(ammAlice.expectTradingFee(500));
-
4508 ammAlice.vote(carol, 0);
-
4509 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
4510 // Carol bids
-
4511 env(ammAlice.bid({.account = carol, .bidMin = 100}));
-
4512 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4'999'900}));
-
4513 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{100}));
-
4514 BEAST_EXPECT(
-
4515 accountBalance(env, carol) ==
-
4516 std::to_string(22500000000 - 4 * baseFee));
-
4517 priceXRP = withdrawByTokens(
-
4518 STAmount{XRPAmount{10'000'000'000}},
-
4519 STAmount{token1, 9'999'900},
-
4520 STAmount{token1, 4'999'900},
-
4521 0);
-
4522 // Carol withdraws
-
4523 ammAlice.withdrawAll(carol, XRP(0));
-
4524 BEAST_EXPECT(
-
4525 accountBalance(env, carol) ==
-
4526 std::to_string(29999949999 - 5 * baseFee));
-
4527 BEAST_EXPECT(ammAlice.expectBalances(
-
4528 XRPAmount{10'000'000'000} - priceXRP,
-
4529 USD(10'000),
-
4530 IOUAmount{5'000'000}));
-
4531 BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
-
4532 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
4533 });
-
4534
-
4535 // Offer crossing with two AMM LPTokens.
-
4536 testAMM([&](AMM& ammAlice, Env& env) {
-
4537 ammAlice.deposit(carol, 1'000'000);
-
4538 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
-
4539 AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000));
-
4540 ammAlice1.deposit(carol, 1'000'000);
-
4541 auto const token1 = ammAlice.lptIssue();
-
4542 auto const token2 = ammAlice1.lptIssue();
-
4543 env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100}),
-
4544 txflags(tfPassive));
-
4545 env.close();
-
4546 BEAST_EXPECT(expectOffers(env, alice, 1));
-
4547 env(offer(carol, STAmount{token2, 100}, STAmount{token1, 100}));
-
4548 env.close();
-
4549 BEAST_EXPECT(
-
4550 expectLine(env, alice, STAmount{token1, 10'000'100}) &&
-
4551 expectLine(env, alice, STAmount{token2, 9'999'900}));
-
4552 BEAST_EXPECT(
-
4553 expectLine(env, carol, STAmount{token2, 1'000'100}) &&
-
4554 expectLine(env, carol, STAmount{token1, 999'900}));
-
4555 BEAST_EXPECT(
-
4556 expectOffers(env, alice, 0) && expectOffers(env, carol, 0));
-
4557 });
-
4558
-
4559 // LPs pay LPTokens directly. Must trust set because the trust line
-
4560 // is checked for the limit, which is 0 in the AMM auto-created
-
4561 // trust line.
-
4562 testAMM([&](AMM& ammAlice, Env& env) {
-
4563 auto const token1 = ammAlice.lptIssue();
-
4564 env.trust(STAmount{token1, 2'000'000}, carol);
-
4565 env.close();
-
4566 ammAlice.deposit(carol, 1'000'000);
-
4567 BEAST_EXPECT(
-
4568 ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
-
4569 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
-
4570 // Pool balance doesn't change, only tokens moved from
-
4571 // one line to another.
-
4572 env(pay(alice, carol, STAmount{token1, 100}));
-
4573 env.close();
-
4574 BEAST_EXPECT(
-
4575 // Alice initial token1 10,000,000 - 100
-
4576 ammAlice.expectLPTokens(alice, IOUAmount{9'999'900, 0}) &&
-
4577 // Carol initial token1 1,000,000 + 100
-
4578 ammAlice.expectLPTokens(carol, IOUAmount{1'000'100, 0}));
-
4579
-
4580 env.trust(STAmount{token1, 20'000'000}, alice);
-
4581 env.close();
-
4582 env(pay(carol, alice, STAmount{token1, 100}));
-
4583 env.close();
-
4584 // Back to the original balance
-
4585 BEAST_EXPECT(
-
4586 ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
-
4587 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
-
4588 });
-
4589 }
-
4590
-
4591 void
-
4592 testAmendment()
-
4593 {
-
4594 testcase("Amendment");
-
4595 using namespace jtx;
-
4596 FeatureBitset const all{supported_amendments()};
-
4597 FeatureBitset const noAMM{all - featureAMM};
-
4598 FeatureBitset const noNumber{all - fixUniversalNumber};
-
4599 FeatureBitset const noAMMAndNumber{
-
4600 all - featureAMM - fixUniversalNumber};
-
4601
-
4602 for (auto const& feature : {noAMM, noNumber, noAMMAndNumber})
-
4603 {
-
4604 Env env{*this, feature};
-
4605 fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
-
4606 AMM amm(env, alice, XRP(1'000), USD(1'000), ter(temDISABLED));
-
4607
-
4608 env(amm.bid({.bidMax = 1000}), ter(temMALFORMED));
-
4609 env(amm.bid({}), ter(temDISABLED));
-
4610 amm.vote(VoteArg{.tfee = 100, .err = ter(temDISABLED)});
-
4611 amm.withdraw(WithdrawArg{.tokens = 100, .err = ter(temMALFORMED)});
-
4612 amm.withdraw(WithdrawArg{.err = ter(temDISABLED)});
-
4613 amm.deposit(
-
4614 DepositArg{.asset1In = USD(100), .err = ter(temDISABLED)});
-
4615 amm.ammDelete(alice, ter(temDISABLED));
-
4616 }
-
4617 }
-
4618
-
4619 void
-
4620 testFlags()
-
4621 {
-
4622 testcase("Flags");
-
4623 using namespace jtx;
-
4624
-
4625 testAMM([&](AMM& ammAlice, Env& env) {
-
4626 auto const info = env.rpc(
-
4627 "json",
-
4628 "account_info",
-
4629 std::string(
-
4630 "{\"account\": \"" + to_string(ammAlice.ammAccount()) +
-
4631 "\"}"));
-
4632 auto const flags =
-
4633 info[jss::result][jss::account_data][jss::Flags].asUInt();
-
4634 BEAST_EXPECT(
-
4635 flags ==
-
4636 (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
-
4637 });
-
4638 }
-
4639
-
4640 void
-
4641 testRippling()
-
4642 {
-
4643 testcase("Rippling");
-
4644 using namespace jtx;
-
4645
-
4646 // Rippling via AMM fails because AMM trust line has 0 limit.
-
4647 // Set up two issuers, A and B. Have each issue a token called TST.
-
4648 // Have another account C hold TST from both issuers,
-
4649 // and create an AMM for this pair.
-
4650 // Have a fourth account, D, create a trust line to the AMM for TST.
-
4651 // Send a payment delivering TST.AMM from C to D, using SendMax in
-
4652 // TST.A (or B) and a path through the AMM account. By normal
-
4653 // rippling rules, this would have caused the AMM's balances
-
4654 // to shift at a 1:1 rate with no fee applied has it not been
-
4655 // for 0 limit.
-
4656 {
-
4657 Env env(*this);
-
4658 auto const A = Account("A");
-
4659 auto const B = Account("B");
-
4660 auto const TSTA = A["TST"];
-
4661 auto const TSTB = B["TST"];
-
4662 auto const C = Account("C");
-
4663 auto const D = Account("D");
-
4664
-
4665 env.fund(XRP(10'000), A);
-
4666 env.fund(XRP(10'000), B);
-
4667 env.fund(XRP(10'000), C);
-
4668 env.fund(XRP(10'000), D);
-
4669
-
4670 env.trust(TSTA(10'000), C);
-
4671 env.trust(TSTB(10'000), C);
-
4672 env(pay(A, C, TSTA(10'000)));
-
4673 env(pay(B, C, TSTB(10'000)));
-
4674 AMM amm(env, C, TSTA(5'000), TSTB(5'000));
-
4675 auto const ammIss = Issue(TSTA.currency, amm.ammAccount());
-
4676
-
4677 // Can SetTrust only for AMM LP tokens
-
4678 env(trust(D, STAmount{ammIss, 10'000}), ter(tecNO_PERMISSION));
-
4679 env.close();
-
4680
-
4681 // The payment would fail because of above, but check just in case
-
4682 env(pay(C, D, STAmount{ammIss, 10}),
-
4683 sendmax(TSTA(100)),
-
4684 path(amm.ammAccount()),
-
4685 txflags(tfPartialPayment | tfNoRippleDirect),
-
4686 ter(tecPATH_DRY));
-
4687 }
-
4688 }
-
4689
-
4690 void
-
4691 testAMMAndCLOB(FeatureBitset features)
-
4692 {
-
4693 testcase("AMMAndCLOB, offer quality change");
-
4694 using namespace jtx;
-
4695 auto const gw = Account("gw");
-
4696 auto const TST = gw["TST"];
-
4697 auto const LP1 = Account("LP1");
-
4698 auto const LP2 = Account("LP2");
-
4699
-
4700 auto prep = [&](auto const& offerCb, auto const& expectCb) {
-
4701 Env env(*this, features);
-
4702 env.fund(XRP(30'000'000'000), gw);
-
4703 env(offer(gw, XRP(11'500'000'000), TST(1'000'000'000)));
-
4704
-
4705 env.fund(XRP(10'000), LP1);
-
4706 env.fund(XRP(10'000), LP2);
-
4707 env(offer(LP1, TST(25), XRPAmount(287'500'000)));
-
4708
-
4709 // Either AMM or CLOB offer
-
4710 offerCb(env);
-
4711
-
4712 env(offer(LP2, TST(25), XRPAmount(287'500'000)));
-
4713
-
4714 expectCb(env);
-
4715 };
-
4716
-
4717 // If we replace AMM with an equivalent CLOB offer, which AMM generates
-
4718 // when it is consumed, then the result must be equivalent, too.
-
4719 std::string lp2TSTBalance;
-
4720 std::string lp2TakerGets;
-
4721 std::string lp2TakerPays;
-
4722 // Execute with AMM first
-
4723 prep(
-
4724 [&](Env& env) { AMM amm(env, LP1, TST(25), XRP(250)); },
-
4725 [&](Env& env) {
-
4726 lp2TSTBalance =
-
4727 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
-
4728 .asString();
-
4729 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
-
4730 lp2TakerGets = offer["taker_gets"].asString();
-
4731 lp2TakerPays = offer["taker_pays"]["value"].asString();
-
4732 });
-
4733 // Execute with CLOB offer
-
4734 prep(
-
4735 [&](Env& env) {
-
4736 if (!features[fixAMMv1_1])
-
4737 env(offer(
-
4738 LP1,
-
4739 XRPAmount{18'095'133},
-
4740 STAmount{TST, UINT64_C(1'68737984885388), -14}),
-
4741 txflags(tfPassive));
-
4742 else
-
4743 env(offer(
-
4744 LP1,
-
4745 XRPAmount{18'095'132},
-
4746 STAmount{TST, UINT64_C(1'68737976189735), -14}),
-
4747 txflags(tfPassive));
-
4748 },
-
4749 [&](Env& env) {
-
4750 BEAST_EXPECT(
-
4751 lp2TSTBalance ==
-
4752 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
-
4753 .asString());
-
4754 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
-
4755 BEAST_EXPECT(lp2TakerGets == offer["taker_gets"].asString());
-
4756 BEAST_EXPECT(
-
4757 lp2TakerPays == offer["taker_pays"]["value"].asString());
-
4758 });
-
4759 }
-
4760
-
4761 void
-
4762 testTradingFee(FeatureBitset features)
-
4763 {
-
4764 testcase("Trading Fee");
-
4765 using namespace jtx;
-
4766
-
4767 // Single Deposit, 1% fee
-
4768 testAMM(
-
4769 [&](AMM& ammAlice, Env& env) {
-
4770 // No fee
-
4771 ammAlice.deposit(carol, USD(3'000));
-
4772 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
-
4773 ammAlice.withdrawAll(carol, USD(3'000));
-
4774 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
4775 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
4776 // Set fee to 1%
-
4777 ammAlice.vote(alice, 1'000);
-
4778 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
-
4779 // Carol gets fewer LPToken ~994, because of the single deposit
-
4780 // fee
-
4781 ammAlice.deposit(carol, USD(3'000));
-
4782 BEAST_EXPECT(ammAlice.expectLPTokens(
-
4783 carol, IOUAmount{994'981155689671, -12}));
-
4784 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
-
4785 // Set fee to 0
-
4786 ammAlice.vote(alice, 0);
-
4787 ammAlice.withdrawAll(carol, USD(0));
-
4788 // Carol gets back less than the original deposit
-
4789 BEAST_EXPECT(expectLine(
-
4790 env,
-
4791 carol,
-
4792 STAmount{USD, UINT64_C(29'994'96220068281), -11}));
-
4793 },
-
4794 {{USD(1'000), EUR(1'000)}},
-
4795 0,
-
4796 std::nullopt,
-
4797 {features});
-
4798
-
4799 // Single deposit with EP not exceeding specified:
-
4800 // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut). 1% fee.
-
4801 testAMM(
-
4802 [&](AMM& ammAlice, Env& env) {
-
4803 auto const balance = env.balance(carol, USD);
-
4804 auto tokensFee = ammAlice.deposit(
-
4805 carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
-
4806 auto const deposit = balance - env.balance(carol, USD);
-
4807 ammAlice.withdrawAll(carol, USD(0));
-
4808 ammAlice.vote(alice, 0);
-
4809 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
4810 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
-
4811 // carol pays ~2008 LPTokens in fees or ~0.5% of the no-fee
-
4812 // LPTokens
-
4813 BEAST_EXPECT(tokensFee == IOUAmount(485'636'0611129, -7));
-
4814 BEAST_EXPECT(tokensNoFee == IOUAmount(487'644'85901109, -8));
-
4815 },
-
4816 std::nullopt,
-
4817 1'000,
-
4818 std::nullopt,
-
4819 {features});
-
4820
-
4821 // Single deposit with EP not exceeding specified:
-
4822 // 200USD with EP not to exceed 0.002020 (AssetIn/TokensOut). 1% fee
-
4823 testAMM(
-
4824 [&](AMM& ammAlice, Env& env) {
-
4825 auto const balance = env.balance(carol, USD);
-
4826 auto const tokensFee = ammAlice.deposit(
-
4827 carol, USD(200), std::nullopt, STAmount{USD, 2020, -6});
-
4828 auto const deposit = balance - env.balance(carol, USD);
-
4829 ammAlice.withdrawAll(carol, USD(0));
-
4830 ammAlice.vote(alice, 0);
-
4831 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
4832 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
-
4833 // carol pays ~475 LPTokens in fees or ~0.5% of the no-fee
-
4834 // LPTokens
-
4835 BEAST_EXPECT(tokensFee == IOUAmount(98'000'00000002, -8));
-
4836 BEAST_EXPECT(tokensNoFee == IOUAmount(98'475'81871545, -8));
-
4837 },
-
4838 std::nullopt,
-
4839 1'000,
-
4840 std::nullopt,
-
4841 {features});
-
4842
-
4843 // Single Withdrawal, 1% fee
-
4844 testAMM(
-
4845 [&](AMM& ammAlice, Env& env) {
-
4846 // No fee
-
4847 ammAlice.deposit(carol, USD(3'000));
-
4848
-
4849 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
-
4850 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
-
4851 // Set fee to 1%
-
4852 ammAlice.vote(alice, 1'000);
-
4853 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
-
4854 // Single withdrawal. Carol gets ~5USD less than deposited.
-
4855 ammAlice.withdrawAll(carol, USD(0));
-
4856 BEAST_EXPECT(expectLine(
-
4857 env,
-
4858 carol,
-
4859 STAmount{USD, UINT64_C(29'994'97487437186), -11}));
-
4860 },
-
4861 {{USD(1'000), EUR(1'000)}},
-
4862 0,
-
4863 std::nullopt,
-
4864 {features});
-
4865
-
4866 // Withdraw with EPrice limit, 1% fee.
-
4867 testAMM(
-
4868 [&](AMM& ammAlice, Env& env) {
-
4869 ammAlice.deposit(carol, 1'000'000);
-
4870 auto const tokensFee = ammAlice.withdraw(
-
4871 carol, USD(100), std::nullopt, IOUAmount{520, 0});
-
4872 // carol withdraws ~1,443.44USD
-
4873 auto const balanceAfterWithdraw = [&]() {
-
4874 if (!features[fixAMMv1_1])
-
4875 return STAmount(USD, UINT64_C(30'443'43891402715), -11);
-
4876 return STAmount(USD, UINT64_C(30'443'43891402714), -11);
-
4877 }();
-
4878 BEAST_EXPECT(env.balance(carol, USD) == balanceAfterWithdraw);
-
4879 // Set to original pool size
-
4880 auto const deposit = balanceAfterWithdraw - USD(29'000);
-
4881 ammAlice.deposit(carol, deposit);
-
4882 // fee 0%
-
4883 ammAlice.vote(alice, 0);
-
4884 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
4885 auto const tokensNoFee = ammAlice.withdraw(carol, deposit);
-
4886 if (!features[fixAMMv1_1])
-
4887 BEAST_EXPECT(
-
4888 env.balance(carol, USD) ==
-
4889 STAmount(USD, UINT64_C(30'443'43891402717), -11));
-
4890 else
-
4891 BEAST_EXPECT(
-
4892 env.balance(carol, USD) ==
-
4893 STAmount(USD, UINT64_C(30'443'43891402716), -11));
-
4894 // carol pays ~4008 LPTokens in fees or ~0.5% of the no-fee
-
4895 // LPTokens
-
4896 if (!features[fixAMMv1_1])
-
4897 BEAST_EXPECT(
-
4898 tokensNoFee == IOUAmount(746'579'80779913, -8));
-
4899 else
-
4900 BEAST_EXPECT(
-
4901 tokensNoFee == IOUAmount(746'579'80779912, -8));
-
4902 BEAST_EXPECT(tokensFee == IOUAmount(750'588'23529411, -8));
-
4903 },
-
4904 std::nullopt,
-
4905 1'000,
-
4906 std::nullopt,
-
4907 {features});
-
4908
-
4909 // Payment, 1% fee
-
4910 testAMM(
-
4911 [&](AMM& ammAlice, Env& env) {
-
4912 fund(
-
4913 env,
-
4914 gw,
-
4915 {bob},
-
4916 XRP(1'000),
-
4917 {USD(1'000), EUR(1'000)},
-
4918 Fund::Acct);
-
4919 // Alice contributed 1010EUR and 1000USD to the pool
-
4920 BEAST_EXPECT(expectLine(env, alice, EUR(28'990)));
-
4921 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
-
4922 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
4923 // Carol pays to Alice with no fee
-
4924 env(pay(carol, alice, EUR(10)),
-
4925 path(~EUR),
-
4926 sendmax(USD(10)),
-
4927 txflags(tfNoRippleDirect));
-
4928 env.close();
-
4929 // Alice has 10EUR more and Carol has 10USD less
-
4930 BEAST_EXPECT(expectLine(env, alice, EUR(29'000)));
-
4931 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
-
4932 BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
-
4933
-
4934 // Set fee to 1%
-
4935 ammAlice.vote(alice, 1'000);
-
4936 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
-
4937 // Bob pays to Carol with 1% fee
-
4938 env(pay(bob, carol, USD(10)),
-
4939 path(~USD),
-
4940 sendmax(EUR(15)),
-
4941 txflags(tfNoRippleDirect));
-
4942 env.close();
-
4943 // Bob sends 10.1~EUR to pay 10USD
-
4944 BEAST_EXPECT(expectLine(
-
4945 env, bob, STAmount{EUR, UINT64_C(989'8989898989899), -13}));
-
4946 // Carol got 10USD
-
4947 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
4948 BEAST_EXPECT(ammAlice.expectBalances(
-
4949 USD(1'000),
-
4950 STAmount{EUR, UINT64_C(1'010'10101010101), -11},
-
4951 ammAlice.tokens()));
-
4952 },
-
4953 {{USD(1'000), EUR(1'010)}},
-
4954 0,
-
4955 std::nullopt,
-
4956 {features});
-
4957
-
4958 // Offer crossing, 0.5% fee
-
4959 testAMM(
-
4960 [&](AMM& ammAlice, Env& env) {
-
4961 // No fee
-
4962 env(offer(carol, EUR(10), USD(10)));
-
4963 env.close();
-
4964 BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
-
4965 BEAST_EXPECT(expectLine(env, carol, EUR(30'010)));
-
4966 // Change pool composition back
-
4967 env(offer(carol, USD(10), EUR(10)));
-
4968 env.close();
-
4969 // Set fee to 0.5%
-
4970 ammAlice.vote(alice, 500);
-
4971 BEAST_EXPECT(ammAlice.expectTradingFee(500));
-
4972 env(offer(carol, EUR(10), USD(10)));
-
4973 env.close();
-
4974 // Alice gets fewer ~4.97EUR for ~5.02USD, the difference goes
-
4975 // to the pool
-
4976 BEAST_EXPECT(expectLine(
-
4977 env,
-
4978 carol,
-
4979 STAmount{USD, UINT64_C(29'995'02512562814), -11}));
-
4980 BEAST_EXPECT(expectLine(
-
4981 env,
-
4982 carol,
-
4983 STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
-
4984 BEAST_EXPECT(expectOffers(
-
4985 env,
-
4986 carol,
-
4987 1,
-
4988 {{Amounts{
-
4989 STAmount{EUR, UINT64_C(5'025125628140703), -15},
-
4990 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
-
4991 if (!features[fixAMMv1_1])
-
4992 {
-
4993 BEAST_EXPECT(ammAlice.expectBalances(
-
4994 STAmount{USD, UINT64_C(1'004'974874371859), -12},
-
4995 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
-
4996 ammAlice.tokens()));
-
4997 }
-
4998 else
-
4999 {
-
5000 BEAST_EXPECT(ammAlice.expectBalances(
-
5001 STAmount{USD, UINT64_C(1'004'97487437186), -11},
-
5002 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
-
5003 ammAlice.tokens()));
-
5004 }
-
5005 },
-
5006 {{USD(1'000), EUR(1'010)}},
-
5007 0,
-
5008 std::nullopt,
-
5009 {features});
-
5010
-
5011 // Payment with AMM and CLOB offer, 0 fee
-
5012 // AMM liquidity is consumed first up to CLOB offer quality
-
5013 // CLOB offer is fully consumed next
-
5014 // Remaining amount is consumed via AMM liquidity
-
5015 {
-
5016 Env env(*this, features);
-
5017 Account const ed("ed");
-
5018 fund(
-
5019 env,
-
5020 gw,
-
5021 {alice, bob, carol, ed},
-
5022 XRP(1'000),
-
5023 {USD(2'000), EUR(2'000)});
-
5024 env(offer(carol, EUR(5), USD(5)));
-
5025 AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
-
5026 env(pay(bob, ed, USD(10)),
-
5027 path(~USD),
-
5028 sendmax(EUR(15)),
-
5029 txflags(tfNoRippleDirect));
-
5030 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
-
5031 if (!features[fixAMMv1_1])
-
5032 {
-
5033 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
-
5034 BEAST_EXPECT(ammAlice.expectBalances(
-
5035 USD(1'000), EUR(1'005), ammAlice.tokens()));
-
5036 }
-
5037 else
-
5038 {
-
5039 BEAST_EXPECT(expectLine(
-
5040 env, bob, STAmount(EUR, UINT64_C(1989'999999999999), -12)));
-
5041 BEAST_EXPECT(ammAlice.expectBalances(
-
5042 USD(1'000),
-
5043 STAmount(EUR, UINT64_C(1005'000000000001), -12),
-
5044 ammAlice.tokens()));
-
5045 }
-
5046 BEAST_EXPECT(expectOffers(env, carol, 0));
-
5047 }
-
5048
-
5049 // Payment with AMM and CLOB offer. Same as above but with 0.25%
-
5050 // fee.
-
5051 {
-
5052 Env env(*this, features);
-
5053 Account const ed("ed");
-
5054 fund(
-
5055 env,
-
5056 gw,
-
5057 {alice, bob, carol, ed},
-
5058 XRP(1'000),
-
5059 {USD(2'000), EUR(2'000)});
-
5060 env(offer(carol, EUR(5), USD(5)));
-
5061 // Set 0.25% fee
-
5062 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 250);
-
5063 env(pay(bob, ed, USD(10)),
-
5064 path(~USD),
-
5065 sendmax(EUR(15)),
-
5066 txflags(tfNoRippleDirect));
-
5067 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
-
5068 if (!features[fixAMMv1_1])
-
5069 {
-
5070 BEAST_EXPECT(expectLine(
-
5071 env,
-
5072 bob,
-
5073 STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
-
5074 BEAST_EXPECT(ammAlice.expectBalances(
-
5075 USD(1'000),
-
5076 STAmount{EUR, UINT64_C(1'005'012546992382), -12},
-
5077 ammAlice.tokens()));
-
5078 }
-
5079 else
-
5080 {
-
5081 BEAST_EXPECT(expectLine(
-
5082 env,
-
5083 bob,
-
5084 STAmount{EUR, UINT64_C(1'989'987453007628), -12}));
-
5085 BEAST_EXPECT(ammAlice.expectBalances(
-
5086 USD(1'000),
-
5087 STAmount{EUR, UINT64_C(1'005'012546992372), -12},
-
5088 ammAlice.tokens()));
-
5089 }
-
5090 BEAST_EXPECT(expectOffers(env, carol, 0));
-
5091 }
-
5092
-
5093 // Payment with AMM and CLOB offer. AMM has a better
-
5094 // spot price quality, but 1% fee offsets that. As the result
-
5095 // the entire trade is executed via LOB.
-
5096 {
-
5097 Env env(*this, features);
-
5098 Account const ed("ed");
-
5099 fund(
-
5100 env,
-
5101 gw,
-
5102 {alice, bob, carol, ed},
-
5103 XRP(1'000),
-
5104 {USD(2'000), EUR(2'000)});
-
5105 env(offer(carol, EUR(10), USD(10)));
-
5106 // Set 1% fee
-
5107 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
-
5108 env(pay(bob, ed, USD(10)),
-
5109 path(~USD),
-
5110 sendmax(EUR(15)),
-
5111 txflags(tfNoRippleDirect));
-
5112 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
-
5113 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
-
5114 BEAST_EXPECT(ammAlice.expectBalances(
-
5115 USD(1'005), EUR(1'000), ammAlice.tokens()));
-
5116 BEAST_EXPECT(expectOffers(env, carol, 0));
-
5117 }
-
5118
-
5119 // Payment with AMM and CLOB offer. AMM has a better
-
5120 // spot price quality, but 1% fee offsets that.
-
5121 // The CLOB offer is consumed first and the remaining
-
5122 // amount is consumed via AMM liquidity.
-
5123 {
-
5124 Env env(*this, features);
-
5125 Account const ed("ed");
-
5126 fund(
-
5127 env,
-
5128 gw,
-
5129 {alice, bob, carol, ed},
-
5130 XRP(1'000),
-
5131 {USD(2'000), EUR(2'000)});
-
5132 env(offer(carol, EUR(9), USD(9)));
-
5133 // Set 1% fee
-
5134 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
-
5135 env(pay(bob, ed, USD(10)),
-
5136 path(~USD),
-
5137 sendmax(EUR(15)),
-
5138 txflags(tfNoRippleDirect));
-
5139 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
-
5140 BEAST_EXPECT(expectLine(
-
5141 env, bob, STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
-
5142 BEAST_EXPECT(ammAlice.expectBalances(
-
5143 USD(1'004),
-
5144 STAmount{EUR, UINT64_C(1'001'006076703288), -12},
-
5145 ammAlice.tokens()));
-
5146 BEAST_EXPECT(expectOffers(env, carol, 0));
-
5147 }
-
5148 }
-
5149
-
5150 void
-
5151 testAdjustedTokens(FeatureBitset features)
-
5152 {
-
5153 testcase("Adjusted Deposit/Withdraw Tokens");
-
5154
-
5155 using namespace jtx;
-
5156
-
5157 // Deposit/Withdraw in USD
-
5158 testAMM(
-
5159 [&](AMM& ammAlice, Env& env) {
-
5160 Account const bob("bob");
-
5161 Account const ed("ed");
-
5162 Account const paul("paul");
-
5163 Account const dan("dan");
-
5164 Account const chris("chris");
-
5165 Account const simon("simon");
-
5166 Account const ben("ben");
-
5167 Account const nataly("nataly");
-
5168 fund(
-
5169 env,
-
5170 gw,
-
5171 {bob, ed, paul, dan, chris, simon, ben, nataly},
-
5172 {USD(1'500'000)},
-
5173 Fund::Acct);
-
5174 for (int i = 0; i < 10; ++i)
-
5175 {
-
5176 ammAlice.deposit(ben, STAmount{USD, 1, -10});
-
5177 ammAlice.withdrawAll(ben, USD(0));
-
5178 ammAlice.deposit(simon, USD(0.1));
-
5179 ammAlice.withdrawAll(simon, USD(0));
-
5180 ammAlice.deposit(chris, USD(1));
-
5181 ammAlice.withdrawAll(chris, USD(0));
-
5182 ammAlice.deposit(dan, USD(10));
-
5183 ammAlice.withdrawAll(dan, USD(0));
-
5184 ammAlice.deposit(bob, USD(100));
-
5185 ammAlice.withdrawAll(bob, USD(0));
-
5186 ammAlice.deposit(carol, USD(1'000));
-
5187 ammAlice.withdrawAll(carol, USD(0));
-
5188 ammAlice.deposit(ed, USD(10'000));
-
5189 ammAlice.withdrawAll(ed, USD(0));
-
5190 ammAlice.deposit(paul, USD(100'000));
-
5191 ammAlice.withdrawAll(paul, USD(0));
-
5192 ammAlice.deposit(nataly, USD(1'000'000));
-
5193 ammAlice.withdrawAll(nataly, USD(0));
-
5194 }
-
5195 // Due to round off some accounts have a tiny gain, while
-
5196 // other have a tiny loss. The last account to withdraw
-
5197 // gets everything in the pool.
-
5198 if (!features[fixAMMv1_1])
-
5199 BEAST_EXPECT(ammAlice.expectBalances(
-
5200 XRP(10'000),
-
5201 STAmount{USD, UINT64_C(10'000'0000000013), -10},
-
5202 IOUAmount{10'000'000}));
-
5203 else
-
5204 BEAST_EXPECT(ammAlice.expectBalances(
-
5205 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
-
5206 BEAST_EXPECT(expectLine(env, ben, USD(1'500'000)));
-
5207 BEAST_EXPECT(expectLine(env, simon, USD(1'500'000)));
-
5208 BEAST_EXPECT(expectLine(env, chris, USD(1'500'000)));
-
5209 BEAST_EXPECT(expectLine(env, dan, USD(1'500'000)));
-
5210 if (!features[fixAMMv1_1])
-
5211 BEAST_EXPECT(expectLine(
-
5212 env,
-
5213 carol,
-
5214 STAmount{USD, UINT64_C(30'000'00000000001), -11}));
-
5215 else
-
5216 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
5217 BEAST_EXPECT(expectLine(env, ed, USD(1'500'000)));
-
5218 BEAST_EXPECT(expectLine(env, paul, USD(1'500'000)));
-
5219 if (!features[fixAMMv1_1])
-
5220 BEAST_EXPECT(expectLine(
-
5221 env,
-
5222 nataly,
-
5223 STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
-
5224 else
-
5225 BEAST_EXPECT(expectLine(
-
5226 env,
-
5227 nataly,
-
5228 STAmount{USD, UINT64_C(1'500'000'000000005), -9}));
-
5229 ammAlice.withdrawAll(alice);
-
5230 BEAST_EXPECT(!ammAlice.ammExists());
-
5231 if (!features[fixAMMv1_1])
-
5232 BEAST_EXPECT(expectLine(
-
5233 env,
-
5234 alice,
-
5235 STAmount{USD, UINT64_C(30'000'0000000013), -10}));
-
5236 else
-
5237 BEAST_EXPECT(expectLine(env, alice, USD(30'000)));
-
5238 // alice XRP balance is 30,000initial - 50 ammcreate fee -
-
5239 // 10drops fee
-
5240 BEAST_EXPECT(
-
5241 accountBalance(env, alice) ==
-
5242 std::to_string(
-
5243 29950000000 - env.current()->fees().base.drops()));
-
5244 },
-
5245 std::nullopt,
-
5246 0,
-
5247 std::nullopt,
-
5248 {features});
-
5249
-
5250 // Same as above but deposit/withdraw in XRP
-
5251 testAMM([&](AMM& ammAlice, Env& env) {
-
5252 Account const bob("bob");
-
5253 Account const ed("ed");
-
5254 Account const paul("paul");
-
5255 Account const dan("dan");
-
5256 Account const chris("chris");
-
5257 Account const simon("simon");
-
5258 Account const ben("ben");
-
5259 Account const nataly("nataly");
-
5260 fund(
-
5261 env,
-
5262 gw,
-
5263 {bob, ed, paul, dan, chris, simon, ben, nataly},
-
5264 XRP(2'000'000),
-
5265 {},
-
5266 Fund::Acct);
-
5267 for (int i = 0; i < 10; ++i)
-
5268 {
-
5269 ammAlice.deposit(ben, XRPAmount{1});
-
5270 ammAlice.withdrawAll(ben, XRP(0));
-
5271 ammAlice.deposit(simon, XRPAmount(1'000));
-
5272 ammAlice.withdrawAll(simon, XRP(0));
-
5273 ammAlice.deposit(chris, XRP(1));
-
5274 ammAlice.withdrawAll(chris, XRP(0));
-
5275 ammAlice.deposit(dan, XRP(10));
-
5276 ammAlice.withdrawAll(dan, XRP(0));
-
5277 ammAlice.deposit(bob, XRP(100));
-
5278 ammAlice.withdrawAll(bob, XRP(0));
-
5279 ammAlice.deposit(carol, XRP(1'000));
-
5280 ammAlice.withdrawAll(carol, XRP(0));
-
5281 ammAlice.deposit(ed, XRP(10'000));
-
5282 ammAlice.withdrawAll(ed, XRP(0));
-
5283 ammAlice.deposit(paul, XRP(100'000));
-
5284 ammAlice.withdrawAll(paul, XRP(0));
-
5285 ammAlice.deposit(nataly, XRP(1'000'000));
-
5286 ammAlice.withdrawAll(nataly, XRP(0));
-
5287 }
-
5288 // No round off with XRP in this test
-
5289 BEAST_EXPECT(ammAlice.expectBalances(
-
5290 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
-
5291 ammAlice.withdrawAll(alice);
-
5292 BEAST_EXPECT(!ammAlice.ammExists());
-
5293 // 20,000 initial - (deposit+withdraw) * 10
-
5294 auto const xrpBalance = (XRP(2'000'000) - txfee(env, 20)).getText();
-
5295 BEAST_EXPECT(accountBalance(env, ben) == xrpBalance);
-
5296 BEAST_EXPECT(accountBalance(env, simon) == xrpBalance);
-
5297 BEAST_EXPECT(accountBalance(env, chris) == xrpBalance);
-
5298 BEAST_EXPECT(accountBalance(env, dan) == xrpBalance);
-
5299
-
5300 auto const baseFee = env.current()->fees().base.drops();
-
5301 // 30,000 initial - (deposit+withdraw) * 10
-
5302 BEAST_EXPECT(
-
5303 accountBalance(env, carol) ==
-
5304 std::to_string(30000000000 - 20 * baseFee));
-
5305 BEAST_EXPECT(accountBalance(env, ed) == xrpBalance);
-
5306 BEAST_EXPECT(accountBalance(env, paul) == xrpBalance);
-
5307 BEAST_EXPECT(accountBalance(env, nataly) == xrpBalance);
-
5308 // 30,000 initial - 50 ammcreate fee - 10drops withdraw fee
-
5309 BEAST_EXPECT(
-
5310 accountBalance(env, alice) ==
-
5311 std::to_string(29950000000 - baseFee));
-
5312 });
-
5313 }
-
5314
-
5315 void
-
5316 testAutoDelete()
-
5317 {
-
5318 testcase("Auto Delete");
-
5319
-
5320 using namespace jtx;
-
5321 FeatureBitset const all{supported_amendments()};
-
5322
-
5323 {
-
5324 Env env(
-
5325 *this,
-
5326 envconfig([](std::unique_ptr<Config> cfg) {
-
5327 cfg->FEES.reference_fee = XRPAmount(1);
-
5328 return cfg;
-
5329 }),
-
5330 all);
-
5331 fund(env, gw, {alice}, XRP(20'000), {USD(10'000)});
-
5332 AMM amm(env, gw, XRP(10'000), USD(10'000));
-
5333 for (auto i = 0; i < maxDeletableAMMTrustLines + 10; ++i)
-
5334 {
-
5335 Account const a{std::to_string(i)};
-
5336 env.fund(XRP(1'000), a);
-
5337 env(trust(a, STAmount{amm.lptIssue(), 10'000}));
-
5338 env.close();
-
5339 }
-
5340 // The trustlines are partially deleted,
-
5341 // AMM is set to an empty state.
-
5342 amm.withdrawAll(gw);
-
5343 BEAST_EXPECT(amm.ammExists());
-
5344
-
5345 // Bid,Vote,Deposit,Withdraw,SetTrust failing with
-
5346 // tecAMM_EMPTY. Deposit succeeds with tfTwoAssetIfEmpty option.
-
5347 env(amm.bid({
-
5348 .account = alice,
-
5349 .bidMin = 1000,
-
5350 }),
-
5351 ter(tecAMM_EMPTY));
-
5352 amm.vote(
-
5353 std::nullopt,
-
5354 100,
-
5355 std::nullopt,
+
2503 std::nullopt,
+
2504 ter(terNO_AMM));
+
2505 });
+
2506 }
+
2507
+
2508 void
+
2509 testFeeVote()
+
2510 {
+
2511 testcase("Fee Vote");
+
2512 using namespace jtx;
+
2513
+
2514 // One vote sets fee to 1%.
+
2515 testAMM([&](AMM& ammAlice, Env& env) {
+
2516 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{0}));
+
2517 ammAlice.vote({}, 1'000);
+
2518 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
+
2519 // Discounted fee is 1/10 of trading fee.
+
2520 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{0}));
+
2521 });
+
2522
+
2523 auto vote = [&](AMM& ammAlice,
+
2524 Env& env,
+
2525 int i,
+
2526 int fundUSD = 100'000,
+
2527 std::uint32_t tokens = 10'000'000,
+
2528 std::vector<Account>* accounts = nullptr) {
+
2529 Account a(std::to_string(i));
+
2530 fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
+
2531 ammAlice.deposit(a, tokens);
+
2532 ammAlice.vote(a, 50 * (i + 1));
+
2533 if (accounts)
+
2534 accounts->push_back(std::move(a));
+
2535 };
+
2536
+
2537 // Eight votes fill all voting slots, set fee 0.175%.
+
2538 testAMM([&](AMM& ammAlice, Env& env) {
+
2539 for (int i = 0; i < 7; ++i)
+
2540 vote(ammAlice, env, i, 10'000);
+
2541 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2542 });
+
2543
+
2544 // Eight votes fill all voting slots, set fee 0.175%.
+
2545 // New vote, same account, sets fee 0.225%
+
2546 testAMM([&](AMM& ammAlice, Env& env) {
+
2547 for (int i = 0; i < 7; ++i)
+
2548 vote(ammAlice, env, i);
+
2549 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2550 Account const a("0");
+
2551 ammAlice.vote(a, 450);
+
2552 BEAST_EXPECT(ammAlice.expectTradingFee(225));
+
2553 });
+
2554
+
2555 // Eight votes fill all voting slots, set fee 0.175%.
+
2556 // New vote, new account, higher vote weight, set higher fee 0.244%
+
2557 testAMM([&](AMM& ammAlice, Env& env) {
+
2558 for (int i = 0; i < 7; ++i)
+
2559 vote(ammAlice, env, i);
+
2560 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2561 vote(ammAlice, env, 7, 100'000, 20'000'000);
+
2562 BEAST_EXPECT(ammAlice.expectTradingFee(244));
+
2563 });
+
2564
+
2565 // Eight votes fill all voting slots, set fee 0.219%.
+
2566 // New vote, new account, higher vote weight, set smaller fee 0.206%
+
2567 testAMM([&](AMM& ammAlice, Env& env) {
+
2568 for (int i = 7; i > 0; --i)
+
2569 vote(ammAlice, env, i);
+
2570 BEAST_EXPECT(ammAlice.expectTradingFee(219));
+
2571 vote(ammAlice, env, 0, 100'000, 20'000'000);
+
2572 BEAST_EXPECT(ammAlice.expectTradingFee(206));
+
2573 });
+
2574
+
2575 // Eight votes fill all voting slots. The accounts then withdraw all
+
2576 // tokens. An account sets a new fee and the previous slots are
+
2577 // deleted.
+
2578 testAMM([&](AMM& ammAlice, Env& env) {
+
2579 std::vector<Account> accounts;
+
2580 for (int i = 0; i < 7; ++i)
+
2581 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
+
2582 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2583 for (int i = 0; i < 7; ++i)
+
2584 ammAlice.withdrawAll(accounts[i]);
+
2585 ammAlice.deposit(carol, 10'000'000);
+
2586 ammAlice.vote(carol, 1'000);
+
2587 // The initial LP set the fee to 1000. Carol gets 50% voting
+
2588 // power, and the new fee is 500.
+
2589 BEAST_EXPECT(ammAlice.expectTradingFee(500));
+
2590 });
+
2591
+
2592 // Eight votes fill all voting slots. The accounts then withdraw some
+
2593 // tokens. The new vote doesn't get the voting power but
+
2594 // the slots are refreshed and the fee is updated.
+
2595 testAMM([&](AMM& ammAlice, Env& env) {
+
2596 std::vector<Account> accounts;
+
2597 for (int i = 0; i < 7; ++i)
+
2598 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
+
2599 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2600 for (int i = 0; i < 7; ++i)
+
2601 ammAlice.withdraw(accounts[i], 9'000'000);
+
2602 ammAlice.deposit(carol, 1'000);
+
2603 // The vote is not added to the slots
+
2604 ammAlice.vote(carol, 1'000);
+
2605 auto const info = ammAlice.ammRpcInfo()[jss::amm][jss::vote_slots];
+
2606 for (std::uint16_t i = 0; i < info.size(); ++i)
+
2607 BEAST_EXPECT(info[i][jss::account] != carol.human());
+
2608 // But the slots are refreshed and the fee is changed
+
2609 BEAST_EXPECT(ammAlice.expectTradingFee(82));
+
2610 });
+
2611 }
+
2612
+
2613 void
+
2614 testInvalidBid()
+
2615 {
+
2616 testcase("Invalid Bid");
+
2617 using namespace jtx;
+
2618 using namespace std::chrono;
+
2619
+
2620 // burn all the LPTokens through a AMMBid transaction
+
2621 {
+
2622 Env env(*this);
+
2623 fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
+
2624 AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
+
2625
+
2626 // auction slot is owned by the creator of the AMM i.e. gw
+
2627 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
+
2628
+
2629 // gw attempts to burn all her LPTokens through a bid transaction
+
2630 // this transaction fails because AMMBid transaction can not burn
+
2631 // all the outstanding LPTokens
+
2632 env(amm.bid({
+
2633 .account = gw,
+
2634 .bidMin = 1'000'000,
+
2635 }),
+
2636 ter(tecAMM_INVALID_TOKENS));
+
2637 }
+
2638
+
2639 // burn all the LPTokens through a AMMBid transaction
+
2640 {
+
2641 Env env(*this);
+
2642 fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
+
2643 AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
+
2644
+
2645 // auction slot is owned by the creator of the AMM i.e. gw
+
2646 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
+
2647
+
2648 // gw burns all but one of its LPTokens through a bid transaction
+
2649 // this transaction suceeds because the bid price is less than
+
2650 // the total outstanding LPToken balance
+
2651 env(amm.bid({
+
2652 .account = gw,
+
2653 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
+
2654 }),
+
2655 ter(tesSUCCESS))
+
2656 .close();
+
2657
+
2658 // gw must own the auction slot
+
2659 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{999'999}));
+
2660
+
2661 // 999'999 tokens are burned, only 1 LPToken is owned by gw
+
2662 BEAST_EXPECT(
+
2663 amm.expectBalances(XRP(1'000), USD(1'000), IOUAmount{1}));
+
2664
+
2665 // gw owns only 1 LPToken in its balance
+
2666 BEAST_EXPECT(Number{amm.getLPTokensBalance(gw)} == 1);
+
2667
+
2668 // gw attempts to burn the last of its LPTokens in an AMMBid
+
2669 // transaction. This transaction fails because it would burn all
+
2670 // the remaining LPTokens
+
2671 env(amm.bid({
+
2672 .account = gw,
+
2673 .bidMin = 1,
+
2674 }),
+
2675 ter(tecAMM_INVALID_TOKENS));
+
2676 }
+
2677
+
2678 testAMM([&](AMM& ammAlice, Env& env) {
+
2679 // Invalid flags
+
2680 env(ammAlice.bid({
+
2681 .account = carol,
+
2682 .bidMin = 0,
+
2683 .flags = tfWithdrawAll,
+
2684 }),
+
2685 ter(temINVALID_FLAG));
+
2686
+
2687 ammAlice.deposit(carol, 1'000'000);
+
2688 // Invalid Bid price <= 0
+
2689 for (auto bid : {0, -100})
+
2690 {
+
2691 env(ammAlice.bid({
+
2692 .account = carol,
+
2693 .bidMin = bid,
+
2694 }),
+
2695 ter(temBAD_AMOUNT));
+
2696 env(ammAlice.bid({
+
2697 .account = carol,
+
2698 .bidMax = bid,
+
2699 }),
+
2700 ter(temBAD_AMOUNT));
+
2701 }
+
2702
+
2703 // Invlaid Min/Max combination
+
2704 env(ammAlice.bid({
+
2705 .account = carol,
+
2706 .bidMin = 200,
+
2707 .bidMax = 100,
+
2708 }),
+
2709 ter(tecAMM_INVALID_TOKENS));
+
2710
+
2711 // Invalid Account
+
2712 Account bad("bad");
+
2713 env.memoize(bad);
+
2714 env(ammAlice.bid({
+
2715 .account = bad,
+
2716 .bidMax = 100,
+
2717 }),
+
2718 seq(1),
+
2719 ter(terNO_ACCOUNT));
+
2720
+
2721 // Account is not LP
+
2722 Account const dan("dan");
+
2723 env.fund(XRP(1'000), dan);
+
2724 env(ammAlice.bid({
+
2725 .account = dan,
+
2726 .bidMin = 100,
+
2727 }),
+
2728 ter(tecAMM_INVALID_TOKENS));
+
2729 env(ammAlice.bid({
+
2730 .account = dan,
+
2731 }),
+
2732 ter(tecAMM_INVALID_TOKENS));
+
2733
+
2734 // Auth account is invalid.
+
2735 env(ammAlice.bid({
+
2736 .account = carol,
+
2737 .bidMin = 100,
+
2738 .authAccounts = {bob},
+
2739 }),
+
2740 ter(terNO_ACCOUNT));
+
2741
+
2742 // Invalid Assets
+
2743 env(ammAlice.bid({
+
2744 .account = alice,
+
2745 .bidMax = 100,
+
2746 .assets = {{USD, GBP}},
+
2747 }),
+
2748 ter(terNO_AMM));
+
2749
+
2750 // Invalid Min/Max issue
+
2751 env(ammAlice.bid({
+
2752 .account = alice,
+
2753 .bidMax = STAmount{USD, 100},
+
2754 }),
+
2755 ter(temBAD_AMM_TOKENS));
+
2756 env(ammAlice.bid({
+
2757 .account = alice,
+
2758 .bidMin = STAmount{USD, 100},
+
2759 }),
+
2760 ter(temBAD_AMM_TOKENS));
+
2761 });
+
2762
+
2763 // Invalid AMM
+
2764 testAMM([&](AMM& ammAlice, Env& env) {
+
2765 ammAlice.withdrawAll(alice);
+
2766 env(ammAlice.bid({
+
2767 .account = alice,
+
2768 .bidMax = 100,
+
2769 }),
+
2770 ter(terNO_AMM));
+
2771 });
+
2772
+
2773 // More than four Auth accounts.
+
2774 testAMM([&](AMM& ammAlice, Env& env) {
+
2775 Account ed("ed");
+
2776 Account bill("bill");
+
2777 Account scott("scott");
+
2778 Account james("james");
+
2779 env.fund(XRP(1'000), bob, ed, bill, scott, james);
+
2780 env.close();
+
2781 ammAlice.deposit(carol, 1'000'000);
+
2782 env(ammAlice.bid({
+
2783 .account = carol,
+
2784 .bidMin = 100,
+
2785 .authAccounts = {bob, ed, bill, scott, james},
+
2786 }),
+
2787 ter(temMALFORMED));
+
2788 });
+
2789
+
2790 // Bid price exceeds LP owned tokens
+
2791 testAMM([&](AMM& ammAlice, Env& env) {
+
2792 fund(env, gw, {bob}, XRP(1'000), {USD(100)}, Fund::Acct);
+
2793 ammAlice.deposit(carol, 1'000'000);
+
2794 ammAlice.deposit(bob, 10);
+
2795 env(ammAlice.bid({
+
2796 .account = carol,
+
2797 .bidMin = 1'000'001,
+
2798 }),
+
2799 ter(tecAMM_INVALID_TOKENS));
+
2800 env(ammAlice.bid({
+
2801 .account = carol,
+
2802 .bidMax = 1'000'001,
+
2803 }),
+
2804 ter(tecAMM_INVALID_TOKENS));
+
2805 env(ammAlice.bid({
+
2806 .account = carol,
+
2807 .bidMin = 1'000,
+
2808 }));
+
2809 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
+
2810 // Slot purchase price is more than 1000 but bob only has 10 tokens
+
2811 env(ammAlice.bid({
+
2812 .account = bob,
+
2813 }),
+
2814 ter(tecAMM_INVALID_TOKENS));
+
2815 });
+
2816
+
2817 // Bid all tokens, still own the slot
+
2818 {
+
2819 Env env(*this);
+
2820 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'000)});
+
2821 AMM amm(env, gw, XRP(10), USD(1'000));
+
2822 auto const lpIssue = amm.lptIssue();
+
2823 env.trust(STAmount{lpIssue, 100}, alice);
+
2824 env.trust(STAmount{lpIssue, 50}, bob);
+
2825 env(pay(gw, alice, STAmount{lpIssue, 100}));
+
2826 env(pay(gw, bob, STAmount{lpIssue, 50}));
+
2827 env(amm.bid({.account = alice, .bidMin = 100}));
+
2828 // Alice doesn't have any more tokens, but
+
2829 // she still owns the slot.
+
2830 env(amm.bid({
+
2831 .account = bob,
+
2832 .bidMax = 50,
+
2833 }),
+
2834 ter(tecAMM_FAILED));
+
2835 }
+
2836 }
+
2837
+
2838 void
+
2839 testBid(FeatureBitset features)
+
2840 {
+
2841 testcase("Bid");
+
2842 using namespace jtx;
+
2843 using namespace std::chrono;
+
2844
+
2845 // Auction slot initially is owned by AMM creator, who pays 0 price.
+
2846
+
2847 // Bid 110 tokens. Pay bidMin.
+
2848 testAMM(
+
2849 [&](AMM& ammAlice, Env& env) {
+
2850 ammAlice.deposit(carol, 1'000'000);
+
2851 env(ammAlice.bid({.account = carol, .bidMin = 110}));
+
2852 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
+
2853 // 110 tokens are burned.
+
2854 BEAST_EXPECT(ammAlice.expectBalances(
+
2855 XRP(11'000), USD(11'000), IOUAmount{10'999'890, 0}));
+
2856 },
+
2857 std::nullopt,
+
2858 0,
+
2859 std::nullopt,
+
2860 {features});
+
2861
+
2862 // Bid with min/max when the pay price is less than min.
+
2863 testAMM(
+
2864 [&](AMM& ammAlice, Env& env) {
+
2865 ammAlice.deposit(carol, 1'000'000);
+
2866 // Bid exactly 110. Pay 110 because the pay price is < 110.
+
2867 env(ammAlice.bid(
+
2868 {.account = carol, .bidMin = 110, .bidMax = 110}));
+
2869 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
+
2870 BEAST_EXPECT(ammAlice.expectBalances(
+
2871 XRP(11'000), USD(11'000), IOUAmount{10'999'890}));
+
2872 // Bid exactly 180-200. Pay 180 because the pay price is < 180.
+
2873 env(ammAlice.bid(
+
2874 {.account = alice, .bidMin = 180, .bidMax = 200}));
+
2875 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{180}));
+
2876 BEAST_EXPECT(ammAlice.expectBalances(
+
2877 XRP(11'000), USD(11'000), IOUAmount{10'999'814'5, -1}));
+
2878 },
+
2879 std::nullopt,
+
2880 0,
+
2881 std::nullopt,
+
2882 {features});
+
2883
+
2884 // Start bid at bidMin 110.
+
2885 testAMM(
+
2886 [&](AMM& ammAlice, Env& env) {
+
2887 ammAlice.deposit(carol, 1'000'000);
+
2888 // Bid, pay bidMin.
+
2889 env(ammAlice.bid({.account = carol, .bidMin = 110}));
+
2890 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
+
2891
+
2892 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
+
2893 ammAlice.deposit(bob, 1'000'000);
+
2894 // Bid, pay the computed price.
+
2895 env(ammAlice.bid({.account = bob}));
+
2896 BEAST_EXPECT(
+
2897 ammAlice.expectAuctionSlot(0, 0, IOUAmount(1155, -1)));
+
2898
+
2899 // Bid bidMax fails because the computed price is higher.
+
2900 env(ammAlice.bid({
+
2901 .account = carol,
+
2902 .bidMax = 120,
+
2903 }),
+
2904 ter(tecAMM_FAILED));
+
2905 // Bid MaxSlotPrice succeeds - pay computed price
+
2906 env(ammAlice.bid({.account = carol, .bidMax = 600}));
+
2907 BEAST_EXPECT(
+
2908 ammAlice.expectAuctionSlot(0, 0, IOUAmount{121'275, -3}));
+
2909
+
2910 // Bid Min/MaxSlotPrice fails because the computed price is not
+
2911 // in range
+
2912 env(ammAlice.bid({
+
2913 .account = carol,
+
2914 .bidMin = 10,
+
2915 .bidMax = 100,
+
2916 }),
+
2917 ter(tecAMM_FAILED));
+
2918 // Bid Min/MaxSlotPrice succeeds - pay computed price
+
2919 env(ammAlice.bid(
+
2920 {.account = carol, .bidMin = 100, .bidMax = 600}));
+
2921 BEAST_EXPECT(
+
2922 ammAlice.expectAuctionSlot(0, 0, IOUAmount{127'33875, -5}));
+
2923 },
+
2924 std::nullopt,
+
2925 0,
+
2926 std::nullopt,
+
2927 {features});
+
2928
+
2929 // Slot states.
+
2930 testAMM(
+
2931 [&](AMM& ammAlice, Env& env) {
+
2932 ammAlice.deposit(carol, 1'000'000);
+
2933
+
2934 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
+
2935 ammAlice.deposit(bob, 1'000'000);
+
2936 BEAST_EXPECT(ammAlice.expectBalances(
+
2937 XRP(12'000), USD(12'000), IOUAmount{12'000'000, 0}));
+
2938
+
2939 // Initial state. Pay bidMin.
+
2940 env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
+
2941 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
+
2942
+
2943 // 1st Interval after close, price for 0th interval.
+
2944 env(ammAlice.bid({.account = bob}));
+
2945 env.close(seconds(AUCTION_SLOT_INTERVAL_DURATION + 1));
+
2946 BEAST_EXPECT(
+
2947 ammAlice.expectAuctionSlot(0, 1, IOUAmount{1'155, -1}));
+
2948
+
2949 // 10th Interval after close, price for 1st interval.
+
2950 env(ammAlice.bid({.account = carol}));
+
2951 env.close(seconds(10 * AUCTION_SLOT_INTERVAL_DURATION + 1));
+
2952 BEAST_EXPECT(
+
2953 ammAlice.expectAuctionSlot(0, 10, IOUAmount{121'275, -3}));
+
2954
+
2955 // 20th Interval (expired) after close, price for 10th interval.
+
2956 env(ammAlice.bid({.account = bob}));
+
2957 env.close(seconds(
+
2958 AUCTION_SLOT_TIME_INTERVALS *
+
2959 AUCTION_SLOT_INTERVAL_DURATION +
+
2960 1));
+
2961 BEAST_EXPECT(ammAlice.expectAuctionSlot(
+
2962 0, std::nullopt, IOUAmount{127'33875, -5}));
+
2963
+
2964 // 0 Interval.
+
2965 env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
+
2966 BEAST_EXPECT(ammAlice.expectAuctionSlot(
+
2967 0, std::nullopt, IOUAmount{110}));
+
2968 // ~321.09 tokens burnt on bidding fees.
+
2969 BEAST_EXPECT(ammAlice.expectBalances(
+
2970 XRP(12'000), USD(12'000), IOUAmount{11'999'678'91, -2}));
+
2971 },
+
2972 std::nullopt,
+
2973 0,
+
2974 std::nullopt,
+
2975 {features});
+
2976
+
2977 // Pool's fee 1%. Bid bidMin.
+
2978 // Auction slot owner and auth account trade at discounted fee -
+
2979 // 1/10 of the trading fee.
+
2980 // Other accounts trade at 1% fee.
+
2981 testAMM(
+
2982 [&](AMM& ammAlice, Env& env) {
+
2983 Account const dan("dan");
+
2984 Account const ed("ed");
+
2985 fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::Acct);
+
2986 ammAlice.deposit(bob, 1'000'000);
+
2987 ammAlice.deposit(ed, 1'000'000);
+
2988 ammAlice.deposit(carol, 500'000);
+
2989 ammAlice.deposit(dan, 500'000);
+
2990 auto ammTokens = ammAlice.getLPTokensBalance();
+
2991 env(ammAlice.bid({
+
2992 .account = carol,
+
2993 .bidMin = 120,
+
2994 .authAccounts = {bob, ed},
+
2995 }));
+
2996 auto const slotPrice = IOUAmount{5'200};
+
2997 ammTokens -= slotPrice;
+
2998 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, slotPrice));
+
2999 BEAST_EXPECT(ammAlice.expectBalances(
+
3000 XRP(13'000), USD(13'000), ammTokens));
+
3001 // Discounted trade
+
3002 for (int i = 0; i < 10; ++i)
+
3003 {
+
3004 auto tokens = ammAlice.deposit(carol, USD(100));
+
3005 ammAlice.withdraw(carol, tokens, USD(0));
+
3006 tokens = ammAlice.deposit(bob, USD(100));
+
3007 ammAlice.withdraw(bob, tokens, USD(0));
+
3008 tokens = ammAlice.deposit(ed, USD(100));
+
3009 ammAlice.withdraw(ed, tokens, USD(0));
+
3010 }
+
3011 // carol, bob, and ed pay ~0.99USD in fees.
+
3012 if (!features[fixAMMv1_1])
+
3013 {
+
3014 BEAST_EXPECT(
+
3015 env.balance(carol, USD) ==
+
3016 STAmount(USD, UINT64_C(29'499'00572620545), -11));
+
3017 BEAST_EXPECT(
+
3018 env.balance(bob, USD) ==
+
3019 STAmount(USD, UINT64_C(18'999'00572616195), -11));
+
3020 BEAST_EXPECT(
+
3021 env.balance(ed, USD) ==
+
3022 STAmount(USD, UINT64_C(18'999'00572611841), -11));
+
3023 // USD pool is slightly higher because of the fees.
+
3024 BEAST_EXPECT(ammAlice.expectBalances(
+
3025 XRP(13'000),
+
3026 STAmount(USD, UINT64_C(13'002'98282151419), -11),
+
3027 ammTokens));
+
3028 }
+
3029 else
+
3030 {
+
3031 BEAST_EXPECT(
+
3032 env.balance(carol, USD) ==
+
3033 STAmount(USD, UINT64_C(29'499'00572620544), -11));
+
3034 BEAST_EXPECT(
+
3035 env.balance(bob, USD) ==
+
3036 STAmount(USD, UINT64_C(18'999'00572616194), -11));
+
3037 BEAST_EXPECT(
+
3038 env.balance(ed, USD) ==
+
3039 STAmount(USD, UINT64_C(18'999'0057261184), -10));
+
3040 // USD pool is slightly higher because of the fees.
+
3041 BEAST_EXPECT(ammAlice.expectBalances(
+
3042 XRP(13'000),
+
3043 STAmount(USD, UINT64_C(13'002'98282151422), -11),
+
3044 ammTokens));
+
3045 }
+
3046 ammTokens = ammAlice.getLPTokensBalance();
+
3047 // Trade with the fee
+
3048 for (int i = 0; i < 10; ++i)
+
3049 {
+
3050 auto const tokens = ammAlice.deposit(dan, USD(100));
+
3051 ammAlice.withdraw(dan, tokens, USD(0));
+
3052 }
+
3053 // dan pays ~9.94USD, which is ~10 times more in fees than
+
3054 // carol, bob, ed. the discounted fee is 10 times less
+
3055 // than the trading fee.
+
3056 if (!features[fixAMMv1_1])
+
3057 {
+
3058 BEAST_EXPECT(
+
3059 env.balance(dan, USD) ==
+
3060 STAmount(USD, UINT64_C(19'490'056722744), -9));
+
3061 // USD pool gains more in dan's fees.
+
3062 BEAST_EXPECT(ammAlice.expectBalances(
+
3063 XRP(13'000),
+
3064 STAmount{USD, UINT64_C(13'012'92609877019), -11},
+
3065 ammTokens));
+
3066 // Discounted fee payment
+
3067 ammAlice.deposit(carol, USD(100));
+
3068 ammTokens = ammAlice.getLPTokensBalance();
+
3069 BEAST_EXPECT(ammAlice.expectBalances(
+
3070 XRP(13'000),
+
3071 STAmount{USD, UINT64_C(13'112'92609877019), -11},
+
3072 ammTokens));
+
3073 env(pay(carol, bob, USD(100)),
+
3074 path(~USD),
+
3075 sendmax(XRP(110)));
+
3076 env.close();
+
3077 // carol pays 100000 drops in fees
+
3078 // 99900668XRP swapped in for 100USD
+
3079 BEAST_EXPECT(ammAlice.expectBalances(
+
3080 XRPAmount{13'100'000'668},
+
3081 STAmount{USD, UINT64_C(13'012'92609877019), -11},
+
3082 ammTokens));
+
3083 }
+
3084 else
+
3085 {
+
3086 BEAST_EXPECT(
+
3087 env.balance(dan, USD) ==
+
3088 STAmount(USD, UINT64_C(19'490'05672274399), -11));
+
3089 // USD pool gains more in dan's fees.
+
3090 BEAST_EXPECT(ammAlice.expectBalances(
+
3091 XRP(13'000),
+
3092 STAmount{USD, UINT64_C(13'012'92609877023), -11},
+
3093 ammTokens));
+
3094 // Discounted fee payment
+
3095 ammAlice.deposit(carol, USD(100));
+
3096 ammTokens = ammAlice.getLPTokensBalance();
+
3097 BEAST_EXPECT(ammAlice.expectBalances(
+
3098 XRP(13'000),
+
3099 STAmount{USD, UINT64_C(13'112'92609877023), -11},
+
3100 ammTokens));
+
3101 env(pay(carol, bob, USD(100)),
+
3102 path(~USD),
+
3103 sendmax(XRP(110)));
+
3104 env.close();
+
3105 // carol pays 100000 drops in fees
+
3106 // 99900668XRP swapped in for 100USD
+
3107 BEAST_EXPECT(ammAlice.expectBalances(
+
3108 XRPAmount{13'100'000'668},
+
3109 STAmount{USD, UINT64_C(13'012'92609877023), -11},
+
3110 ammTokens));
+
3111 }
+
3112 // Payment with the trading fee
+
3113 env(pay(alice, carol, XRP(100)), path(~XRP), sendmax(USD(110)));
+
3114 env.close();
+
3115 // alice pays ~1.011USD in fees, which is ~10 times more
+
3116 // than carol's fee
+
3117 // 100.099431529USD swapped in for 100XRP
+
3118 if (!features[fixAMMv1_1])
+
3119 {
+
3120 BEAST_EXPECT(ammAlice.expectBalances(
+
3121 XRPAmount{13'000'000'668},
+
3122 STAmount{USD, UINT64_C(13'114'03663047264), -11},
+
3123 ammTokens));
+
3124 }
+
3125 else
+
3126 {
+
3127 BEAST_EXPECT(ammAlice.expectBalances(
+
3128 XRPAmount{13'000'000'668},
+
3129 STAmount{USD, UINT64_C(13'114'03663047269), -11},
+
3130 ammTokens));
+
3131 }
+
3132 // Auction slot expired, no discounted fee
+
3133 env.close(seconds(TOTAL_TIME_SLOT_SECS + 1));
+
3134 // clock is parent's based
+
3135 env.close();
+
3136 if (!features[fixAMMv1_1])
+
3137 BEAST_EXPECT(
+
3138 env.balance(carol, USD) ==
+
3139 STAmount(USD, UINT64_C(29'399'00572620545), -11));
+
3140 else
+
3141 BEAST_EXPECT(
+
3142 env.balance(carol, USD) ==
+
3143 STAmount(USD, UINT64_C(29'399'00572620544), -11));
+
3144 ammTokens = ammAlice.getLPTokensBalance();
+
3145 for (int i = 0; i < 10; ++i)
+
3146 {
+
3147 auto const tokens = ammAlice.deposit(carol, USD(100));
+
3148 ammAlice.withdraw(carol, tokens, USD(0));
+
3149 }
+
3150 // carol pays ~9.94USD in fees, which is ~10 times more in
+
3151 // trading fees vs discounted fee.
+
3152 if (!features[fixAMMv1_1])
+
3153 {
+
3154 BEAST_EXPECT(
+
3155 env.balance(carol, USD) ==
+
3156 STAmount(USD, UINT64_C(29'389'06197177128), -11));
+
3157 BEAST_EXPECT(ammAlice.expectBalances(
+
3158 XRPAmount{13'000'000'668},
+
3159 STAmount{USD, UINT64_C(13'123'98038490681), -11},
+
3160 ammTokens));
+
3161 }
+
3162 else
+
3163 {
+
3164 BEAST_EXPECT(
+
3165 env.balance(carol, USD) ==
+
3166 STAmount(USD, UINT64_C(29'389'06197177124), -11));
+
3167 BEAST_EXPECT(ammAlice.expectBalances(
+
3168 XRPAmount{13'000'000'668},
+
3169 STAmount{USD, UINT64_C(13'123'98038490689), -11},
+
3170 ammTokens));
+
3171 }
+
3172 env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110)));
+
3173 env.close();
+
3174 // carol pays ~1.008XRP in trading fee, which is
+
3175 // ~10 times more than the discounted fee.
+
3176 // 99.815876XRP is swapped in for 100USD
+
3177 if (!features[fixAMMv1_1])
+
3178 {
+
3179 BEAST_EXPECT(ammAlice.expectBalances(
+
3180 XRPAmount(13'100'824'790),
+
3181 STAmount{USD, UINT64_C(13'023'98038490681), -11},
+
3182 ammTokens));
+
3183 }
+
3184 else
+
3185 {
+
3186 BEAST_EXPECT(ammAlice.expectBalances(
+
3187 XRPAmount(13'100'824'790),
+
3188 STAmount{USD, UINT64_C(13'023'98038490689), -11},
+
3189 ammTokens));
+
3190 }
+
3191 },
+
3192 std::nullopt,
+
3193 1'000,
+
3194 std::nullopt,
+
3195 {features});
+
3196
+
3197 // Bid tiny amount
+
3198 testAMM(
+
3199 [&](AMM& ammAlice, Env& env) {
+
3200 // Bid a tiny amount
+
3201 auto const tiny =
+
3202 Number{STAmount::cMinValue, STAmount::cMinOffset};
+
3203 env(ammAlice.bid(
+
3204 {.account = alice, .bidMin = IOUAmount{tiny}}));
+
3205 // Auction slot purchase price is equal to the tiny amount
+
3206 // since the minSlotPrice is 0 with no trading fee.
+
3207 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
+
3208 // The purchase price is too small to affect the total tokens
+
3209 BEAST_EXPECT(ammAlice.expectBalances(
+
3210 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
3211 // Bid the tiny amount
+
3212 env(ammAlice.bid({
+
3213 .account = alice,
+
3214 .bidMin =
+
3215 IOUAmount{STAmount::cMinValue, STAmount::cMinOffset},
+
3216 }));
+
3217 // Pay slightly higher price
+
3218 BEAST_EXPECT(ammAlice.expectAuctionSlot(
+
3219 0, 0, IOUAmount{tiny * Number{105, -2}}));
+
3220 // The purchase price is still too small to affect the total
+
3221 // tokens
+
3222 BEAST_EXPECT(ammAlice.expectBalances(
+
3223 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
3224 },
+
3225 std::nullopt,
+
3226 0,
+
3227 std::nullopt,
+
3228 {features});
+
3229
+
3230 // Reset auth account
+
3231 testAMM(
+
3232 [&](AMM& ammAlice, Env& env) {
+
3233 env(ammAlice.bid({
+
3234 .account = alice,
+
3235 .bidMin = IOUAmount{100},
+
3236 .authAccounts = {carol},
+
3237 }));
+
3238 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
+
3239 env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}}));
+
3240 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
+
3241 Account bob("bob");
+
3242 Account dan("dan");
+
3243 fund(env, {bob, dan}, XRP(1'000));
+
3244 env(ammAlice.bid({
+
3245 .account = alice,
+
3246 .bidMin = IOUAmount{100},
+
3247 .authAccounts = {bob, dan},
+
3248 }));
+
3249 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
+
3250 },
+
3251 std::nullopt,
+
3252 0,
+
3253 std::nullopt,
+
3254 {features});
+
3255
+
3256 // Bid all tokens, still own the slot and trade at a discount
+
3257 {
+
3258 Env env(*this, features);
+
3259 fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
+
3260 AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
+
3261 auto const lpIssue = amm.lptIssue();
+
3262 env.trust(STAmount{lpIssue, 500}, alice);
+
3263 env.trust(STAmount{lpIssue, 50}, bob);
+
3264 env(pay(gw, alice, STAmount{lpIssue, 500}));
+
3265 env(pay(gw, bob, STAmount{lpIssue, 50}));
+
3266 // Alice doesn't have anymore lp tokens
+
3267 env(amm.bid({.account = alice, .bidMin = 500}));
+
3268 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{500}));
+
3269 BEAST_EXPECT(expectLine(env, alice, STAmount{lpIssue, 0}));
+
3270 // But trades with the discounted fee since she still owns the slot.
+
3271 // Alice pays 10011 drops in fees
+
3272 env(pay(alice, bob, USD(10)), path(~USD), sendmax(XRP(11)));
+
3273 BEAST_EXPECT(amm.expectBalances(
+
3274 XRPAmount{1'010'010'011},
+
3275 USD(1'000),
+
3276 IOUAmount{1'004'487'562112089, -9}));
+
3277 // Bob pays the full fee ~0.1USD
+
3278 env(pay(bob, alice, XRP(10)), path(~XRP), sendmax(USD(11)));
+
3279 if (!features[fixAMMv1_1])
+
3280 {
+
3281 BEAST_EXPECT(amm.expectBalances(
+
3282 XRPAmount{1'000'010'011},
+
3283 STAmount{USD, UINT64_C(1'010'10090898081), -11},
+
3284 IOUAmount{1'004'487'562112089, -9}));
+
3285 }
+
3286 else
+
3287 {
+
3288 BEAST_EXPECT(amm.expectBalances(
+
3289 XRPAmount{1'000'010'011},
+
3290 STAmount{USD, UINT64_C(1'010'100908980811), -12},
+
3291 IOUAmount{1'004'487'562112089, -9}));
+
3292 }
+
3293 }
+
3294
+
3295 // preflight tests
+
3296 {
+
3297 Env env(*this, features);
+
3298 auto const baseFee = env.current()->fees().base;
+
3299
+
3300 fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
+
3301 AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
+
3302 Json::Value tx = amm.bid({.account = alice, .bidMin = 500});
+
3303
+
3304 {
+
3305 auto jtx = env.jt(tx, seq(1), fee(baseFee));
+
3306 env.app().config().features.erase(featureAMM);
+
3307 PreflightContext pfctx(
+
3308 env.app(),
+
3309 *jtx.stx,
+
3310 env.current()->rules(),
+
3311 tapNONE,
+
3312 env.journal);
+
3313 auto pf = AMMBid::preflight(pfctx);
+
3314 BEAST_EXPECT(pf == temDISABLED);
+
3315 env.app().config().features.insert(featureAMM);
+
3316 }
+
3317
+
3318 {
+
3319 auto jtx = env.jt(tx, seq(1), fee(baseFee));
+
3320 jtx.jv["TxnSignature"] = "deadbeef";
+
3321 jtx.stx = env.ust(jtx);
+
3322 PreflightContext pfctx(
+
3323 env.app(),
+
3324 *jtx.stx,
+
3325 env.current()->rules(),
+
3326 tapNONE,
+
3327 env.journal);
+
3328 auto pf = AMMBid::preflight(pfctx);
+
3329 BEAST_EXPECT(pf != tesSUCCESS);
+
3330 }
+
3331
+
3332 {
+
3333 auto jtx = env.jt(tx, seq(1), fee(baseFee));
+
3334 jtx.jv["Asset2"]["currency"] = "XRP";
+
3335 jtx.jv["Asset2"].removeMember("issuer");
+
3336 jtx.stx = env.ust(jtx);
+
3337 PreflightContext pfctx(
+
3338 env.app(),
+
3339 *jtx.stx,
+
3340 env.current()->rules(),
+
3341 tapNONE,
+
3342 env.journal);
+
3343 auto pf = AMMBid::preflight(pfctx);
+
3344 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
+
3345 }
+
3346 }
+
3347 }
+
3348
+
3349 void
+
3350 testInvalidAMMPayment()
+
3351 {
+
3352 testcase("Invalid AMM Payment");
+
3353 using namespace jtx;
+
3354 using namespace std::chrono;
+
3355 using namespace std::literals::chrono_literals;
+
3356
+
3357 // Can't pay into AMM account.
+
3358 // Can't pay out since there is no keys
+
3359 for (auto const& acct : {gw, alice})
+
3360 {
+
3361 {
+
3362 Env env(*this);
+
3363 fund(env, gw, {alice, carol}, XRP(1'000), {USD(100)});
+
3364 // XRP balance is below reserve
+
3365 AMM ammAlice(env, acct, XRP(10), USD(10));
+
3366 // Pay below reserve
+
3367 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
+
3368 ter(tecNO_PERMISSION));
+
3369 // Pay above reserve
+
3370 env(pay(carol, ammAlice.ammAccount(), XRP(300)),
+
3371 ter(tecNO_PERMISSION));
+
3372 // Pay IOU
+
3373 env(pay(carol, ammAlice.ammAccount(), USD(10)),
+
3374 ter(tecNO_PERMISSION));
+
3375 }
+
3376 {
+
3377 Env env(*this);
+
3378 fund(env, gw, {alice, carol}, XRP(10'000'000), {USD(10'000)});
+
3379 // XRP balance is above reserve
+
3380 AMM ammAlice(env, acct, XRP(1'000'000), USD(100));
+
3381 // Pay below reserve
+
3382 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
+
3383 ter(tecNO_PERMISSION));
+
3384 // Pay above reserve
+
3385 env(pay(carol, ammAlice.ammAccount(), XRP(1'000'000)),
+
3386 ter(tecNO_PERMISSION));
+
3387 }
+
3388 }
+
3389
+
3390 // Can't pay into AMM with escrow.
+
3391 testAMM([&](AMM& ammAlice, Env& env) {
+
3392 auto const baseFee = env.current()->fees().base;
+
3393 env(escrow(carol, ammAlice.ammAccount(), XRP(1)),
+
3394 condition(cb1),
+
3395 finish_time(env.now() + 1s),
+
3396 cancel_time(env.now() + 2s),
+
3397 fee(baseFee * 150),
+
3398 ter(tecNO_PERMISSION));
+
3399 });
+
3400
+
3401 // Can't pay into AMM with paychan.
+
3402 testAMM([&](AMM& ammAlice, Env& env) {
+
3403 auto const pk = carol.pk();
+
3404 auto const settleDelay = 100s;
+
3405 NetClock::time_point const cancelAfter =
+
3406 env.current()->info().parentCloseTime + 200s;
+
3407 env(create(
+
3408 carol,
+
3409 ammAlice.ammAccount(),
+
3410 XRP(1'000),
+
3411 settleDelay,
+
3412 pk,
+
3413 cancelAfter),
+
3414 ter(tecNO_PERMISSION));
+
3415 });
+
3416
+
3417 // Can't pay into AMM with checks.
+
3418 testAMM([&](AMM& ammAlice, Env& env) {
+
3419 env(check::create(env.master.id(), ammAlice.ammAccount(), XRP(100)),
+
3420 ter(tecNO_PERMISSION));
+
3421 });
+
3422
+
3423 // Pay amounts close to one side of the pool
+
3424 testAMM(
+
3425 [&](AMM& ammAlice, Env& env) {
+
3426 // Can't consume whole pool
+
3427 env(pay(alice, carol, USD(100)),
+
3428 path(~USD),
+
3429 sendmax(XRP(1'000'000'000)),
+
3430 ter(tecPATH_PARTIAL));
+
3431 env(pay(alice, carol, XRP(100)),
+
3432 path(~XRP),
+
3433 sendmax(USD(1'000'000'000)),
+
3434 ter(tecPATH_PARTIAL));
+
3435 // Overflow
+
3436 env(pay(alice,
+
3437 carol,
+
3438 STAmount{USD, UINT64_C(99'999999999), -9}),
+
3439 path(~USD),
+
3440 sendmax(XRP(1'000'000'000)),
+
3441 ter(tecPATH_PARTIAL));
+
3442 env(pay(alice,
+
3443 carol,
+
3444 STAmount{USD, UINT64_C(999'99999999), -8}),
+
3445 path(~USD),
+
3446 sendmax(XRP(1'000'000'000)),
+
3447 ter(tecPATH_PARTIAL));
+
3448 env(pay(alice, carol, STAmount{xrpIssue(), 99'999'999}),
+
3449 path(~XRP),
+
3450 sendmax(USD(1'000'000'000)),
+
3451 ter(tecPATH_PARTIAL));
+
3452 // Sender doesn't have enough funds
+
3453 env(pay(alice, carol, USD(99.99)),
+
3454 path(~USD),
+
3455 sendmax(XRP(1'000'000'000)),
+
3456 ter(tecPATH_PARTIAL));
+
3457 env(pay(alice, carol, STAmount{xrpIssue(), 99'990'000}),
+
3458 path(~XRP),
+
3459 sendmax(USD(1'000'000'000)),
+
3460 ter(tecPATH_PARTIAL));
+
3461 },
+
3462 {{XRP(100), USD(100)}});
+
3463
+
3464 // Globally frozen
+
3465 testAMM([&](AMM& ammAlice, Env& env) {
+
3466 env(fset(gw, asfGlobalFreeze));
+
3467 env.close();
+
3468 env(pay(alice, carol, USD(1)),
+
3469 path(~USD),
+
3470 txflags(tfPartialPayment | tfNoRippleDirect),
+
3471 sendmax(XRP(10)),
+
3472 ter(tecPATH_DRY));
+
3473 env(pay(alice, carol, XRP(1)),
+
3474 path(~XRP),
+
3475 txflags(tfPartialPayment | tfNoRippleDirect),
+
3476 sendmax(USD(10)),
+
3477 ter(tecPATH_DRY));
+
3478 });
+
3479
+
3480 // Individually frozen AMM
+
3481 testAMM([&](AMM& ammAlice, Env& env) {
+
3482 env(trust(
+
3483 gw,
+
3484 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
+
3485 tfSetFreeze));
+
3486 env.close();
+
3487 env(pay(alice, carol, USD(1)),
+
3488 path(~USD),
+
3489 txflags(tfPartialPayment | tfNoRippleDirect),
+
3490 sendmax(XRP(10)),
+
3491 ter(tecPATH_DRY));
+
3492 env(pay(alice, carol, XRP(1)),
+
3493 path(~XRP),
+
3494 txflags(tfPartialPayment | tfNoRippleDirect),
+
3495 sendmax(USD(10)),
+
3496 ter(tecPATH_DRY));
+
3497 });
+
3498
+
3499 // Individually frozen accounts
+
3500 testAMM([&](AMM& ammAlice, Env& env) {
+
3501 env(trust(gw, carol["USD"](0), tfSetFreeze));
+
3502 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
3503 env.close();
+
3504 env(pay(alice, carol, XRP(1)),
+
3505 path(~XRP),
+
3506 sendmax(USD(10)),
+
3507 txflags(tfNoRippleDirect | tfPartialPayment),
+
3508 ter(tecPATH_DRY));
+
3509 });
+
3510 }
+
3511
+
3512 void
+
3513 testBasicPaymentEngine(FeatureBitset features)
+
3514 {
+
3515 testcase("Basic Payment");
+
3516 using namespace jtx;
+
3517
+
3518 // Payment 100USD for 100XRP.
+
3519 // Force one path with tfNoRippleDirect.
+
3520 testAMM(
+
3521 [&](AMM& ammAlice, Env& env) {
+
3522 env.fund(jtx::XRP(30'000), bob);
+
3523 env.close();
+
3524 env(pay(bob, carol, USD(100)),
+
3525 path(~USD),
+
3526 sendmax(XRP(100)),
+
3527 txflags(tfNoRippleDirect));
+
3528 env.close();
+
3529 BEAST_EXPECT(ammAlice.expectBalances(
+
3530 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
3531 // Initial balance 30,000 + 100
+
3532 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
3533 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
+
3534 BEAST_EXPECT(expectLedgerEntryRoot(
+
3535 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
+
3536 },
+
3537 {{XRP(10'000), USD(10'100)}},
+
3538 0,
+
3539 std::nullopt,
+
3540 {features});
+
3541
+
3542 // Payment 100USD for 100XRP, use default path.
+
3543 testAMM(
+
3544 [&](AMM& ammAlice, Env& env) {
+
3545 env.fund(jtx::XRP(30'000), bob);
+
3546 env.close();
+
3547 env(pay(bob, carol, USD(100)), sendmax(XRP(100)));
+
3548 env.close();
+
3549 BEAST_EXPECT(ammAlice.expectBalances(
+
3550 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
3551 // Initial balance 30,000 + 100
+
3552 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
3553 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
+
3554 BEAST_EXPECT(expectLedgerEntryRoot(
+
3555 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
+
3556 },
+
3557 {{XRP(10'000), USD(10'100)}},
+
3558 0,
+
3559 std::nullopt,
+
3560 {features});
+
3561
+
3562 // This payment is identical to above. While it has
+
3563 // both default path and path, activeStrands has one path.
+
3564 testAMM(
+
3565 [&](AMM& ammAlice, Env& env) {
+
3566 env.fund(jtx::XRP(30'000), bob);
+
3567 env.close();
+
3568 env(pay(bob, carol, USD(100)), path(~USD), sendmax(XRP(100)));
+
3569 env.close();
+
3570 BEAST_EXPECT(ammAlice.expectBalances(
+
3571 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
3572 // Initial balance 30,000 + 100
+
3573 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
3574 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
+
3575 BEAST_EXPECT(expectLedgerEntryRoot(
+
3576 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
+
3577 },
+
3578 {{XRP(10'000), USD(10'100)}},
+
3579 0,
+
3580 std::nullopt,
+
3581 {features});
+
3582
+
3583 // Payment with limitQuality set.
+
3584 testAMM(
+
3585 [&](AMM& ammAlice, Env& env) {
+
3586 env.fund(jtx::XRP(30'000), bob);
+
3587 env.close();
+
3588 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
+
3589 // would have been sent has it not been for limitQuality.
+
3590 env(pay(bob, carol, USD(100)),
+
3591 path(~USD),
+
3592 sendmax(XRP(100)),
+
3593 txflags(
+
3594 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
3595 env.close();
+
3596 BEAST_EXPECT(ammAlice.expectBalances(
+
3597 XRP(10'010), USD(10'000), ammAlice.tokens()));
+
3598 // Initial balance 30,000 + 10(limited by limitQuality)
+
3599 BEAST_EXPECT(expectLine(env, carol, USD(30'010)));
+
3600 // Initial balance 30,000 - 10(limited by limitQuality) - 10(tx
+
3601 // fee)
+
3602 BEAST_EXPECT(expectLedgerEntryRoot(
+
3603 env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
+
3604
+
3605 // Fails because of limitQuality. Would have sent
+
3606 // ~98.91USD/110XRP has it not been for limitQuality.
+
3607 env(pay(bob, carol, USD(100)),
+
3608 path(~USD),
+
3609 sendmax(XRP(100)),
+
3610 txflags(
+
3611 tfNoRippleDirect | tfPartialPayment | tfLimitQuality),
+
3612 ter(tecPATH_DRY));
+
3613 env.close();
+
3614 },
+
3615 {{XRP(10'000), USD(10'010)}},
+
3616 0,
+
3617 std::nullopt,
+
3618 {features});
+
3619
+
3620 // Payment with limitQuality and transfer fee set.
+
3621 testAMM(
+
3622 [&](AMM& ammAlice, Env& env) {
+
3623 env(rate(gw, 1.1));
+
3624 env.close();
+
3625 env.fund(jtx::XRP(30'000), bob);
+
3626 env.close();
+
3627 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
+
3628 // would have been sent has it not been for limitQuality and
+
3629 // the transfer fee.
+
3630 env(pay(bob, carol, USD(100)),
+
3631 path(~USD),
+
3632 sendmax(XRP(110)),
+
3633 txflags(
+
3634 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
3635 env.close();
+
3636 BEAST_EXPECT(ammAlice.expectBalances(
+
3637 XRP(10'010), USD(10'000), ammAlice.tokens()));
+
3638 // 10USD - 10% transfer fee
+
3639 BEAST_EXPECT(expectLine(
+
3640 env,
+
3641 carol,
+
3642 STAmount{USD, UINT64_C(30'009'09090909091), -11}));
+
3643 BEAST_EXPECT(expectLedgerEntryRoot(
+
3644 env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
+
3645 },
+
3646 {{XRP(10'000), USD(10'010)}},
+
3647 0,
+
3648 std::nullopt,
+
3649 {features});
+
3650
+
3651 // Fail when partial payment is not set.
+
3652 testAMM(
+
3653 [&](AMM& ammAlice, Env& env) {
+
3654 env.fund(jtx::XRP(30'000), bob);
+
3655 env.close();
+
3656 env(pay(bob, carol, USD(100)),
+
3657 path(~USD),
+
3658 sendmax(XRP(100)),
+
3659 txflags(tfNoRippleDirect),
+
3660 ter(tecPATH_PARTIAL));
+
3661 },
+
3662 {{XRP(10'000), USD(10'000)}},
+
3663 0,
+
3664 std::nullopt,
+
3665 {features});
+
3666
+
3667 // Non-default path (with AMM) has a better quality than default path.
+
3668 // The max possible liquidity is taken out of non-default
+
3669 // path ~29.9XRP/29.9EUR, ~29.9EUR/~29.99USD. The rest
+
3670 // is taken from the offer.
+
3671 {
+
3672 Env env(*this, features);
+
3673 fund(
+
3674 env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
+
3675 env.close();
+
3676 env.fund(XRP(1'000), bob);
+
3677 env.close();
+
3678 auto ammEUR_XRP = AMM(env, alice, XRP(10'000), EUR(10'000));
+
3679 auto ammUSD_EUR = AMM(env, alice, EUR(10'000), USD(10'000));
+
3680 env(offer(alice, XRP(101), USD(100)), txflags(tfPassive));
+
3681 env.close();
+
3682 env(pay(bob, carol, USD(100)),
+
3683 path(~EUR, ~USD),
+
3684 sendmax(XRP(102)),
+
3685 txflags(tfPartialPayment));
+
3686 env.close();
+
3687 BEAST_EXPECT(ammEUR_XRP.expectBalances(
+
3688 XRPAmount(10'030'082'730),
+
3689 STAmount(EUR, UINT64_C(9'970'007498125468), -12),
+
3690 ammEUR_XRP.tokens()));
+
3691 if (!features[fixAMMv1_1])
+
3692 {
+
3693 BEAST_EXPECT(ammUSD_EUR.expectBalances(
+
3694 STAmount(USD, UINT64_C(9'970'097277662122), -12),
+
3695 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
+
3696 ammUSD_EUR.tokens()));
+
3697
+
3698 // fixReducedOffersV2 changes the expected results slightly.
+
3699 Amounts const expectedAmounts =
+
3700 env.closed()->rules().enabled(fixReducedOffersV2)
+
3701 ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233787816), -14)}
+
3702 : Amounts{
+
3703 XRPAmount(30'201'749),
+
3704 STAmount(USD, UINT64_C(29'90272233787818), -14)};
+
3705
+
3706 BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}}));
+
3707 }
+
3708 else
+
3709 {
+
3710 BEAST_EXPECT(ammUSD_EUR.expectBalances(
+
3711 STAmount(USD, UINT64_C(9'970'097277662172), -12),
+
3712 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
+
3713 ammUSD_EUR.tokens()));
+
3714
+
3715 // fixReducedOffersV2 changes the expected results slightly.
+
3716 Amounts const expectedAmounts =
+
3717 env.closed()->rules().enabled(fixReducedOffersV2)
+
3718 ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233782839), -14)}
+
3719 : Amounts{
+
3720 XRPAmount(30'201'749),
+
3721 STAmount(USD, UINT64_C(29'90272233782840), -14)};
+
3722
+
3723 BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}}));
+
3724 }
+
3725 // Initial 30,000 + 100
+
3726 BEAST_EXPECT(expectLine(env, carol, STAmount{USD, 30'100}));
+
3727 // Initial 1,000 - 30082730(AMM pool) - 70798251(offer) - 10(tx fee)
+
3728 BEAST_EXPECT(expectLedgerEntryRoot(
+
3729 env,
+
3730 bob,
+
3731 XRP(1'000) - XRPAmount{30'082'730} - XRPAmount{70'798'251} -
+
3732 txfee(env, 1)));
+
3733 }
+
3734
+
3735 // Default path (with AMM) has a better quality than a non-default path.
+
3736 // The max possible liquidity is taken out of default
+
3737 // path ~49XRP/49USD. The rest is taken from the offer.
+
3738 testAMM(
+
3739 [&](AMM& ammAlice, Env& env) {
+
3740 env.fund(XRP(1'000), bob);
+
3741 env.close();
+
3742 env.trust(EUR(2'000), alice);
+
3743 env.close();
+
3744 env(pay(gw, alice, EUR(1'000)));
+
3745 env(offer(alice, XRP(101), EUR(100)), txflags(tfPassive));
+
3746 env.close();
+
3747 env(offer(alice, EUR(100), USD(100)), txflags(tfPassive));
+
3748 env.close();
+
3749 env(pay(bob, carol, USD(100)),
+
3750 path(~EUR, ~USD),
+
3751 sendmax(XRP(102)),
+
3752 txflags(tfPartialPayment));
+
3753 env.close();
+
3754 BEAST_EXPECT(ammAlice.expectBalances(
+
3755 XRPAmount(10'050'238'637),
+
3756 STAmount(USD, UINT64_C(9'950'01249687578), -11),
+
3757 ammAlice.tokens()));
+
3758 BEAST_EXPECT(expectOffers(
+
3759 env,
+
3760 alice,
+
3761 2,
+
3762 {{Amounts{
+
3763 XRPAmount(50'487'378),
+
3764 STAmount(EUR, UINT64_C(49'98750312422), -11)},
+
3765 Amounts{
+
3766 STAmount(EUR, UINT64_C(49'98750312422), -11),
+
3767 STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
+
3768 // Initial 30,000 + 99.99999999999
+
3769 BEAST_EXPECT(expectLine(
+
3770 env,
+
3771 carol,
+
3772 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
+
3773 // Initial 1,000 - 50238637(AMM pool) - 50512622(offer) - 10(tx
+
3774 // fee)
+
3775 BEAST_EXPECT(expectLedgerEntryRoot(
+
3776 env,
+
3777 bob,
+
3778 XRP(1'000) - XRPAmount{50'238'637} - XRPAmount{50'512'622} -
+
3779 txfee(env, 1)));
+
3780 },
+
3781 std::nullopt,
+
3782 0,
+
3783 std::nullopt,
+
3784 {features});
+
3785
+
3786 // Default path with AMM and Order Book offer. AMM is consumed first,
+
3787 // remaining amount is consumed by the offer.
+
3788 testAMM(
+
3789 [&](AMM& ammAlice, Env& env) {
+
3790 fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
+
3791 env.close();
+
3792 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
+
3793 env.close();
+
3794 env(pay(alice, carol, USD(200)),
+
3795 sendmax(XRP(200)),
+
3796 txflags(tfPartialPayment));
+
3797 env.close();
+
3798 if (!features[fixAMMv1_1])
+
3799 {
+
3800 BEAST_EXPECT(ammAlice.expectBalances(
+
3801 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
3802 // Initial 30,000 + 200
+
3803 BEAST_EXPECT(expectLine(env, carol, USD(30'200)));
+
3804 }
+
3805 else
+
3806 {
+
3807 BEAST_EXPECT(ammAlice.expectBalances(
+
3808 XRP(10'100),
+
3809 STAmount(USD, UINT64_C(10'000'00000000001), -11),
+
3810 ammAlice.tokens()));
+
3811 BEAST_EXPECT(expectLine(
+
3812 env,
+
3813 carol,
+
3814 STAmount(USD, UINT64_C(30'199'99999999999), -11)));
+
3815 }
+
3816 // Initial 30,000 - 10000(AMM pool LP) - 100(AMM offer) -
+
3817 // - 100(offer) - 10(tx fee) - one reserve
+
3818 BEAST_EXPECT(expectLedgerEntryRoot(
+
3819 env,
+
3820 alice,
+
3821 XRP(30'000) - XRP(10'000) - XRP(100) - XRP(100) -
+
3822 ammCrtFee(env) - txfee(env, 1)));
+
3823 BEAST_EXPECT(expectOffers(env, bob, 0));
+
3824 },
+
3825 {{XRP(10'000), USD(10'100)}},
+
3826 0,
+
3827 std::nullopt,
+
3828 {features});
+
3829
+
3830 // Default path with AMM and Order Book offer.
+
3831 // Order Book offer is consumed first.
+
3832 // Remaining amount is consumed by AMM.
+
3833 {
+
3834 Env env(*this, features);
+
3835 fund(env, gw, {alice, bob, carol}, XRP(20'000), {USD(2'000)});
+
3836 env.close();
+
3837 env(offer(bob, XRP(50), USD(150)), txflags(tfPassive));
+
3838 env.close();
+
3839 AMM ammAlice(env, alice, XRP(1'000), USD(1'050));
+
3840 env(pay(alice, carol, USD(200)),
+
3841 sendmax(XRP(200)),
+
3842 txflags(tfPartialPayment));
+
3843 env.close();
+
3844 BEAST_EXPECT(ammAlice.expectBalances(
+
3845 XRP(1'050), USD(1'000), ammAlice.tokens()));
+
3846 BEAST_EXPECT(expectLine(env, carol, USD(2'200)));
+
3847 BEAST_EXPECT(expectOffers(env, bob, 0));
+
3848 }
+
3849
+
3850 // Offer crossing XRP/IOU
+
3851 testAMM(
+
3852 [&](AMM& ammAlice, Env& env) {
+
3853 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
+
3854 env.close();
+
3855 env(offer(bob, USD(100), XRP(100)));
+
3856 env.close();
+
3857 BEAST_EXPECT(ammAlice.expectBalances(
+
3858 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
3859 // Initial 1,000 + 100
+
3860 BEAST_EXPECT(expectLine(env, bob, USD(1'100)));
+
3861 // Initial 30,000 - 100(offer) - 10(tx fee)
+
3862 BEAST_EXPECT(expectLedgerEntryRoot(
+
3863 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
+
3864 BEAST_EXPECT(expectOffers(env, bob, 0));
+
3865 },
+
3866 {{XRP(10'000), USD(10'100)}},
+
3867 0,
+
3868 std::nullopt,
+
3869 {features});
+
3870
+
3871 // Offer crossing IOU/IOU and transfer rate
+
3872 // Single path AMM offer
+
3873 testAMM(
+
3874 [&](AMM& ammAlice, Env& env) {
+
3875 env(rate(gw, 1.25));
+
3876 env.close();
+
3877 // This offer succeeds to cross pre- and post-amendment
+
3878 // because the strand's out amount is small enough to match
+
3879 // limitQuality value and limitOut() function in StrandFlow
+
3880 // doesn't require an adjustment to out value.
+
3881 env(offer(carol, EUR(100), GBP(100)));
+
3882 env.close();
+
3883 // No transfer fee
+
3884 BEAST_EXPECT(ammAlice.expectBalances(
+
3885 GBP(1'100), EUR(1'000), ammAlice.tokens()));
+
3886 // Initial 30,000 - 100(offer) - 25% transfer fee
+
3887 BEAST_EXPECT(expectLine(env, carol, GBP(29'875)));
+
3888 // Initial 30,000 + 100(offer)
+
3889 BEAST_EXPECT(expectLine(env, carol, EUR(30'100)));
+
3890 BEAST_EXPECT(expectOffers(env, bob, 0));
+
3891 },
+
3892 {{GBP(1'000), EUR(1'100)}},
+
3893 0,
+
3894 std::nullopt,
+
3895 {features});
+
3896 // Single-path AMM offer
+
3897 testAMM(
+
3898 [&](AMM& amm, Env& env) {
+
3899 env(rate(gw, 1.001));
+
3900 env.close();
+
3901 env(offer(carol, XRP(100), USD(55)));
+
3902 env.close();
+
3903 if (!features[fixAMMv1_1])
+
3904 {
+
3905 // Pre-amendment the transfer fee is not taken into
+
3906 // account when calculating the limit out based on
+
3907 // limitQuality. Carol pays 0.1% on the takerGets, which
+
3908 // lowers the overall quality. AMM offer is generated based
+
3909 // on higher limit out, which generates a larger offer
+
3910 // with lower quality. Consequently, the offer fails
+
3911 // to cross.
+
3912 BEAST_EXPECT(
+
3913 amm.expectBalances(XRP(1'000), USD(500), amm.tokens()));
+
3914 BEAST_EXPECT(expectOffers(
+
3915 env, carol, 1, {{Amounts{XRP(100), USD(55)}}}));
+
3916 }
+
3917 else
+
3918 {
+
3919 // Post-amendment the transfer fee is taken into account
+
3920 // when calculating the limit out based on limitQuality.
+
3921 // This increases the limitQuality and decreases
+
3922 // the limit out. Consequently, AMM offer size is decreased,
+
3923 // and the quality is increased, matching the overall
+
3924 // quality.
+
3925 // AMM offer ~50USD/91XRP
+
3926 BEAST_EXPECT(amm.expectBalances(
+
3927 XRPAmount(909'090'909),
+
3928 STAmount{USD, UINT64_C(550'000000055), -9},
+
3929 amm.tokens()));
+
3930 // Offer ~91XRP/49.99USD
+
3931 BEAST_EXPECT(expectOffers(
+
3932 env,
+
3933 carol,
+
3934 1,
+
3935 {{Amounts{
+
3936 XRPAmount{9'090'909},
+
3937 STAmount{USD, 4'99999995, -8}}}}));
+
3938 // Carol pays 0.1% fee on ~50USD =~ 0.05USD
+
3939 BEAST_EXPECT(
+
3940 env.balance(carol, USD) ==
+
3941 STAmount(USD, UINT64_C(29'949'94999999494), -11));
+
3942 }
+
3943 },
+
3944 {{XRP(1'000), USD(500)}},
+
3945 0,
+
3946 std::nullopt,
+
3947 {features});
+
3948 testAMM(
+
3949 [&](AMM& amm, Env& env) {
+
3950 env(rate(gw, 1.001));
+
3951 env.close();
+
3952 env(offer(carol, XRP(10), USD(5.5)));
+
3953 env.close();
+
3954 if (!features[fixAMMv1_1])
+
3955 {
+
3956 BEAST_EXPECT(amm.expectBalances(
+
3957 XRP(990),
+
3958 STAmount{USD, UINT64_C(505'050505050505), -12},
+
3959 amm.tokens()));
+
3960 BEAST_EXPECT(expectOffers(env, carol, 0));
+
3961 }
+
3962 else
+
3963 {
+
3964 BEAST_EXPECT(amm.expectBalances(
+
3965 XRP(990),
+
3966 STAmount{USD, UINT64_C(505'0505050505051), -13},
+
3967 amm.tokens()));
+
3968 BEAST_EXPECT(expectOffers(env, carol, 0));
+
3969 }
+
3970 },
+
3971 {{XRP(1'000), USD(500)}},
+
3972 0,
+
3973 std::nullopt,
+
3974 {features});
+
3975 // Multi-path AMM offer
+
3976 testAMM(
+
3977 [&](AMM& ammAlice, Env& env) {
+
3978 Account const ed("ed");
+
3979 fund(
+
3980 env,
+
3981 gw,
+
3982 {bob, ed},
+
3983 XRP(30'000),
+
3984 {GBP(2'000), EUR(2'000)},
+
3985 Fund::Acct);
+
3986 env(rate(gw, 1.25));
+
3987 env.close();
+
3988 // The auto-bridge is worse quality than AMM, is not consumed
+
3989 // first and initially forces multi-path AMM offer generation.
+
3990 // Multi-path AMM offers are consumed until their quality
+
3991 // is less than the auto-bridge offers quality. Auto-bridge
+
3992 // offers are consumed afterward. Then the behavior is
+
3993 // different pre-amendment and post-amendment.
+
3994 env(offer(bob, GBP(10), XRP(10)), txflags(tfPassive));
+
3995 env(offer(ed, XRP(10), EUR(10)), txflags(tfPassive));
+
3996 env.close();
+
3997 env(offer(carol, EUR(100), GBP(100)));
+
3998 env.close();
+
3999 if (!features[fixAMMv1_1])
+
4000 {
+
4001 // After the auto-bridge offers are consumed, single path
+
4002 // AMM offer is generated with the limit out not taking
+
4003 // into consideration the transfer fee. This results
+
4004 // in an overall lower quality offer than the limit quality
+
4005 // and the single path AMM offer fails to consume.
+
4006 // Total consumed ~37.06GBP/39.32EUR
+
4007 BEAST_EXPECT(ammAlice.expectBalances(
+
4008 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
+
4009 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
+
4010 ammAlice.tokens()));
+
4011 // Consumed offer ~49.32EUR/49.32GBP
+
4012 BEAST_EXPECT(expectOffers(
+
4013 env,
+
4014 carol,
+
4015 1,
+
4016 {Amounts{
+
4017 STAmount{EUR, UINT64_C(50'684828792831), -12},
+
4018 STAmount{GBP, UINT64_C(50'684828792831), -12}}}));
+
4019 BEAST_EXPECT(expectOffers(env, bob, 0));
+
4020 BEAST_EXPECT(expectOffers(env, ed, 0));
+
4021
+
4022 // Initial 30,000 - ~47.06(offers = 37.06(AMM) + 10(LOB))
+
4023 // * 1.25
+
4024 // = 58.825 = ~29941.17
+
4025 // carol bought ~72.93EUR at the cost of ~70.68GBP
+
4026 // the offer is partially consumed
+
4027 BEAST_EXPECT(expectLine(
+
4028 env,
+
4029 carol,
+
4030 STAmount{GBP, UINT64_C(29'941'16770347333), -11}));
+
4031 // Initial 30,000 + ~49.3(offers = 39.3(AMM) + 10(LOB))
+
4032 BEAST_EXPECT(expectLine(
+
4033 env,
+
4034 carol,
+
4035 STAmount{EUR, UINT64_C(30'049'31517120716), -11}));
+
4036 }
+
4037 else
+
4038 {
+
4039 // After the auto-bridge offers are consumed, single path
+
4040 // AMM offer is generated with the limit out taking
+
4041 // into consideration the transfer fee. This results
+
4042 // in an overall quality offer matching the limit quality
+
4043 // and the single path AMM offer is consumed. More
+
4044 // liquidity is consumed overall in post-amendment.
+
4045 // Total consumed ~60.68GBP/62.93EUR
+
4046 BEAST_EXPECT(ammAlice.expectBalances(
+
4047 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
+
4048 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
+
4049 ammAlice.tokens()));
+
4050 // Consumed offer ~72.93EUR/72.93GBP
+
4051 BEAST_EXPECT(expectOffers(
+
4052 env,
+
4053 carol,
+
4054 1,
+
4055 {Amounts{
+
4056 STAmount{EUR, UINT64_C(27'06583722134028), -14},
+
4057 STAmount{GBP, UINT64_C(27'06583722134028), -14}}}));
+
4058 BEAST_EXPECT(expectOffers(env, bob, 0));
+
4059 BEAST_EXPECT(expectOffers(env, ed, 0));
+
4060
+
4061 // Initial 30,000 - ~70.68(offers = 60.68(AMM) + 10(LOB))
+
4062 // * 1.25
+
4063 // = 88.35 = ~29911.64
+
4064 // carol bought ~72.93EUR at the cost of ~70.68GBP
+
4065 // the offer is partially consumed
+
4066 BEAST_EXPECT(expectLine(
+
4067 env,
+
4068 carol,
+
4069 STAmount{GBP, UINT64_C(29'911'64396400896), -11}));
+
4070 // Initial 30,000 + ~72.93(offers = 62.93(AMM) + 10(LOB))
+
4071 BEAST_EXPECT(expectLine(
+
4072 env,
+
4073 carol,
+
4074 STAmount{EUR, UINT64_C(30'072'93416277865), -11}));
+
4075 }
+
4076 // Initial 2000 + 10 = 2010
+
4077 BEAST_EXPECT(expectLine(env, bob, GBP(2'010)));
+
4078 // Initial 2000 - 10 * 1.25 = 1987.5
+
4079 BEAST_EXPECT(expectLine(env, ed, EUR(1'987.5)));
+
4080 },
+
4081 {{GBP(1'000), EUR(1'100)}},
+
4082 0,
+
4083 std::nullopt,
+
4084 {features});
+
4085
+
4086 // Payment and transfer fee
+
4087 // Scenario:
+
4088 // Bob sends 125GBP to pay 80EUR to Carol
+
4089 // Payment execution:
+
4090 // bob's 125GBP/1.25 = 100GBP
+
4091 // 100GBP/100EUR AMM offer
+
4092 // 100EUR/1.25 = 80EUR paid to carol
+
4093 testAMM(
+
4094 [&](AMM& ammAlice, Env& env) {
+
4095 fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
+
4096 env(rate(gw, 1.25));
+
4097 env.close();
+
4098 env(pay(bob, carol, EUR(100)),
+
4099 path(~EUR),
+
4100 sendmax(GBP(125)),
+
4101 txflags(tfPartialPayment));
+
4102 env.close();
+
4103 BEAST_EXPECT(ammAlice.expectBalances(
+
4104 GBP(1'100), EUR(1'000), ammAlice.tokens()));
+
4105 BEAST_EXPECT(expectLine(env, bob, GBP(75)));
+
4106 BEAST_EXPECT(expectLine(env, carol, EUR(30'080)));
+
4107 },
+
4108 {{GBP(1'000), EUR(1'100)}},
+
4109 0,
+
4110 std::nullopt,
+
4111 {features});
+
4112
+
4113 // Payment and transfer fee, multiple steps
+
4114 // Scenario:
+
4115 // Dan's offer 200CAN/200GBP
+
4116 // AMM 1000GBP/10125EUR
+
4117 // Ed's offer 200EUR/200USD
+
4118 // Bob sends 195.3125CAN to pay 100USD to Carol
+
4119 // Payment execution:
+
4120 // bob's 195.3125CAN/1.25 = 156.25CAN -> dan's offer
+
4121 // 156.25CAN/156.25GBP 156.25GBP/1.25 = 125GBP -> AMM's offer
+
4122 // 125GBP/125EUR 125EUR/1.25 = 100EUR -> ed's offer
+
4123 // 100EUR/100USD 100USD/1.25 = 80USD paid to carol
+
4124 testAMM(
+
4125 [&](AMM& ammAlice, Env& env) {
+
4126 Account const dan("dan");
+
4127 Account const ed("ed");
+
4128 auto const CAN = gw["CAN"];
+
4129 fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
+
4130 fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
+
4131 fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
+
4132 env(trust(carol, USD(100)));
+
4133 env(rate(gw, 1.25));
+
4134 env.close();
+
4135 env(offer(dan, CAN(200), GBP(200)));
+
4136 env(offer(ed, EUR(200), USD(200)));
+
4137 env.close();
+
4138 env(pay(bob, carol, USD(100)),
+
4139 path(~GBP, ~EUR, ~USD),
+
4140 sendmax(CAN(195.3125)),
+
4141 txflags(tfPartialPayment));
+
4142 env.close();
+
4143 BEAST_EXPECT(expectLine(env, bob, CAN(0)));
+
4144 BEAST_EXPECT(expectLine(env, dan, CAN(356.25), GBP(43.75)));
+
4145 BEAST_EXPECT(ammAlice.expectBalances(
+
4146 GBP(10'125), EUR(10'000), ammAlice.tokens()));
+
4147 BEAST_EXPECT(expectLine(env, ed, EUR(300), USD(100)));
+
4148 BEAST_EXPECT(expectLine(env, carol, USD(80)));
+
4149 },
+
4150 {{GBP(10'000), EUR(10'125)}},
+
4151 0,
+
4152 std::nullopt,
+
4153 {features});
+
4154
+
4155 // Pay amounts close to one side of the pool
+
4156 testAMM(
+
4157 [&](AMM& ammAlice, Env& env) {
+
4158 env(pay(alice, carol, USD(99.99)),
+
4159 path(~USD),
+
4160 sendmax(XRP(1)),
+
4161 txflags(tfPartialPayment),
+
4162 ter(tesSUCCESS));
+
4163 env(pay(alice, carol, USD(100)),
+
4164 path(~USD),
+
4165 sendmax(XRP(1)),
+
4166 txflags(tfPartialPayment),
+
4167 ter(tesSUCCESS));
+
4168 env(pay(alice, carol, XRP(100)),
+
4169 path(~XRP),
+
4170 sendmax(USD(1)),
+
4171 txflags(tfPartialPayment),
+
4172 ter(tesSUCCESS));
+
4173 env(pay(alice, carol, STAmount{xrpIssue(), 99'999'900}),
+
4174 path(~XRP),
+
4175 sendmax(USD(1)),
+
4176 txflags(tfPartialPayment),
+
4177 ter(tesSUCCESS));
+
4178 },
+
4179 {{XRP(100), USD(100)}},
+
4180 0,
+
4181 std::nullopt,
+
4182 {features});
+
4183
+
4184 // Multiple paths/steps
+
4185 {
+
4186 Env env(*this, features);
+
4187 auto const ETH = gw["ETH"];
+
4188 fund(
+
4189 env,
+
4190 gw,
+
4191 {alice},
+
4192 XRP(100'000),
+
4193 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
+
4194 fund(env, gw, {carol, bob}, XRP(1'000), {USD(200)}, Fund::Acct);
+
4195 AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
+
4196 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
+
4197 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
+
4198 AMM xrp_usd(env, alice, XRP(10'150), USD(10'200));
+
4199 AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
+
4200 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
+
4201 AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
+
4202 env(pay(bob, carol, USD(100)),
+
4203 path(~EUR, ~BTC, ~USD),
+
4204 path(~USD),
+
4205 path(~ETH, ~EUR, ~USD),
+
4206 sendmax(XRP(200)));
+
4207 if (!features[fixAMMv1_1])
+
4208 {
+
4209 // XRP-ETH-EUR-USD
+
4210 // This path provides ~26.06USD/26.2XRP
+
4211 BEAST_EXPECT(xrp_eth.expectBalances(
+
4212 XRPAmount(10'026'208'900),
+
4213 STAmount{ETH, UINT64_C(10'073'65779244494), -11},
+
4214 xrp_eth.tokens()));
+
4215 BEAST_EXPECT(eth_eur.expectBalances(
+
4216 STAmount{ETH, UINT64_C(10'926'34220755506), -11},
+
4217 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
+
4218 eth_eur.tokens()));
+
4219 BEAST_EXPECT(eur_usd.expectBalances(
+
4220 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
+
4221 STAmount{USD, UINT64_C(9'973'93151712086), -11},
+
4222 eur_usd.tokens()));
+
4223 // XRP-USD path
+
4224 // This path provides ~73.9USD/74.1XRP
+
4225 BEAST_EXPECT(xrp_usd.expectBalances(
+
4226 XRPAmount(10'224'106'246),
+
4227 STAmount{USD, UINT64_C(10'126'06848287914), -11},
+
4228 xrp_usd.tokens()));
+
4229 }
+
4230 else
+
4231 {
+
4232 BEAST_EXPECT(xrp_eth.expectBalances(
+
4233 XRPAmount(10'026'208'900),
+
4234 STAmount{ETH, UINT64_C(10'073'65779244461), -11},
+
4235 xrp_eth.tokens()));
+
4236 BEAST_EXPECT(eth_eur.expectBalances(
+
4237 STAmount{ETH, UINT64_C(10'926'34220755539), -11},
+
4238 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
+
4239 eth_eur.tokens()));
+
4240 BEAST_EXPECT(eur_usd.expectBalances(
+
4241 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
+
4242 STAmount{USD, UINT64_C(9'973'93151712057), -11},
+
4243 eur_usd.tokens()));
+
4244 // XRP-USD path
+
4245 // This path provides ~73.9USD/74.1XRP
+
4246 BEAST_EXPECT(xrp_usd.expectBalances(
+
4247 XRPAmount(10'224'106'246),
+
4248 STAmount{USD, UINT64_C(10'126'06848287943), -11},
+
4249 xrp_usd.tokens()));
+
4250 }
+
4251
+
4252 // XRP-EUR-BTC-USD
+
4253 // This path doesn't provide any liquidity due to how
+
4254 // offers are generated in multi-path. Analytical solution
+
4255 // shows a different distribution:
+
4256 // XRP-EUR-BTC-USD 11.6USD/11.64XRP, XRP-USD 60.7USD/60.8XRP,
+
4257 // XRP-ETH-EUR-USD 27.6USD/27.6XRP
+
4258 BEAST_EXPECT(xrp_eur.expectBalances(
+
4259 XRP(10'100), EUR(10'000), xrp_eur.tokens()));
+
4260 BEAST_EXPECT(eur_btc.expectBalances(
+
4261 EUR(10'000), BTC(10'200), eur_btc.tokens()));
+
4262 BEAST_EXPECT(btc_usd.expectBalances(
+
4263 BTC(10'100), USD(10'000), btc_usd.tokens()));
+
4264
+
4265 BEAST_EXPECT(expectLine(env, carol, USD(300)));
+
4266 }
+
4267
+
4268 // Dependent AMM
+
4269 {
+
4270 Env env(*this, features);
+
4271 auto const ETH = gw["ETH"];
+
4272 fund(
+
4273 env,
+
4274 gw,
+
4275 {alice},
+
4276 XRP(40'000),
+
4277 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
+
4278 fund(env, gw, {carol, bob}, XRP(1000), {USD(200)}, Fund::Acct);
+
4279 AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
+
4280 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
+
4281 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
+
4282 AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
+
4283 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
+
4284 env(pay(bob, carol, USD(100)),
+
4285 path(~EUR, ~BTC, ~USD),
+
4286 path(~ETH, ~EUR, ~BTC, ~USD),
+
4287 sendmax(XRP(200)));
+
4288 if (!features[fixAMMv1_1])
+
4289 {
+
4290 // XRP-EUR-BTC-USD path provides ~17.8USD/~18.7XRP
+
4291 // XRP-ETH-EUR-BTC-USD path provides ~82.2USD/82.4XRP
+
4292 BEAST_EXPECT(xrp_eur.expectBalances(
+
4293 XRPAmount(10'118'738'472),
+
4294 STAmount{EUR, UINT64_C(9'981'544436337968), -12},
+
4295 xrp_eur.tokens()));
+
4296 BEAST_EXPECT(eur_btc.expectBalances(
+
4297 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
+
4298 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
+
4299 eur_btc.tokens()));
+
4300 BEAST_EXPECT(btc_usd.expectBalances(
+
4301 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
+
4302 USD(9'900),
+
4303 btc_usd.tokens()));
+
4304 BEAST_EXPECT(xrp_eth.expectBalances(
+
4305 XRPAmount(10'082'446'397),
+
4306 STAmount{ETH, UINT64_C(10'017'41072778012), -11},
+
4307 xrp_eth.tokens()));
+
4308 BEAST_EXPECT(eth_eur.expectBalances(
+
4309 STAmount{ETH, UINT64_C(10'982'58927221988), -11},
+
4310 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
+
4311 eth_eur.tokens()));
+
4312 }
+
4313 else
+
4314 {
+
4315 BEAST_EXPECT(xrp_eur.expectBalances(
+
4316 XRPAmount(10'118'738'472),
+
4317 STAmount{EUR, UINT64_C(9'981'544436337923), -12},
+
4318 xrp_eur.tokens()));
+
4319 BEAST_EXPECT(eur_btc.expectBalances(
+
4320 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
+
4321 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
+
4322 eur_btc.tokens()));
+
4323 BEAST_EXPECT(btc_usd.expectBalances(
+
4324 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
+
4325 USD(9'900),
+
4326 btc_usd.tokens()));
+
4327 BEAST_EXPECT(xrp_eth.expectBalances(
+
4328 XRPAmount(10'082'446'397),
+
4329 STAmount{ETH, UINT64_C(10'017'41072777996), -11},
+
4330 xrp_eth.tokens()));
+
4331 BEAST_EXPECT(eth_eur.expectBalances(
+
4332 STAmount{ETH, UINT64_C(10'982'58927222004), -11},
+
4333 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
+
4334 eth_eur.tokens()));
+
4335 }
+
4336 BEAST_EXPECT(expectLine(env, carol, USD(300)));
+
4337 }
+
4338
+
4339 // AMM offers limit
+
4340 // Consuming 30 CLOB offers, results in hitting 30 AMM offers limit.
+
4341 testAMM(
+
4342 [&](AMM& ammAlice, Env& env) {
+
4343 env.fund(XRP(1'000), bob);
+
4344 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
+
4345 env(trust(alice, EUR(200)));
+
4346 for (int i = 0; i < 30; ++i)
+
4347 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
+
4348 // This is worse quality offer than 30 offers above.
+
4349 // It will not be consumed because of AMM offers limit.
+
4350 env(offer(alice, EUR(140), XRP(100)));
+
4351 env(pay(bob, carol, USD(100)),
+
4352 path(~XRP, ~USD),
+
4353 sendmax(EUR(400)),
+
4354 txflags(tfPartialPayment | tfNoRippleDirect));
+
4355 if (!features[fixAMMv1_1])
+
4356 {
+
4357 // Carol gets ~29.91USD because of the AMM offers limit
+
4358 BEAST_EXPECT(ammAlice.expectBalances(
+
4359 XRP(10'030),
+
4360 STAmount{USD, UINT64_C(9'970'089730807577), -12},
+
4361 ammAlice.tokens()));
+
4362 BEAST_EXPECT(expectLine(
+
4363 env,
+
4364 carol,
+
4365 STAmount{USD, UINT64_C(30'029'91026919241), -11}));
+
4366 }
+
4367 else
+
4368 {
+
4369 BEAST_EXPECT(ammAlice.expectBalances(
+
4370 XRP(10'030),
+
4371 STAmount{USD, UINT64_C(9'970'089730807827), -12},
+
4372 ammAlice.tokens()));
+
4373 BEAST_EXPECT(expectLine(
+
4374 env,
+
4375 carol,
+
4376 STAmount{USD, UINT64_C(30'029'91026919217), -11}));
+
4377 }
+
4378 BEAST_EXPECT(
+
4379 expectOffers(env, alice, 1, {{{EUR(140), XRP(100)}}}));
+
4380 },
+
4381 std::nullopt,
+
4382 0,
+
4383 std::nullopt,
+
4384 {features});
+
4385 // This payment is fulfilled
+
4386 testAMM(
+
4387 [&](AMM& ammAlice, Env& env) {
+
4388 env.fund(XRP(1'000), bob);
+
4389 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
+
4390 env(trust(alice, EUR(200)));
+
4391 for (int i = 0; i < 29; ++i)
+
4392 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
+
4393 // This is worse quality offer than 30 offers above.
+
4394 // It will not be consumed because of AMM offers limit.
+
4395 env(offer(alice, EUR(140), XRP(100)));
+
4396 env(pay(bob, carol, USD(100)),
+
4397 path(~XRP, ~USD),
+
4398 sendmax(EUR(400)),
+
4399 txflags(tfPartialPayment | tfNoRippleDirect));
+
4400 BEAST_EXPECT(ammAlice.expectBalances(
+
4401 XRPAmount{10'101'010'102}, USD(9'900), ammAlice.tokens()));
+
4402 if (!features[fixAMMv1_1])
+
4403 {
+
4404 // Carol gets ~100USD
+
4405 BEAST_EXPECT(expectLine(
+
4406 env,
+
4407 carol,
+
4408 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
+
4409 }
+
4410 else
+
4411 {
+
4412 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
4413 }
+
4414 BEAST_EXPECT(expectOffers(
+
4415 env,
+
4416 alice,
+
4417 1,
+
4418 {{{STAmount{EUR, UINT64_C(39'1858572), -7},
+
4419 XRPAmount{27'989'898}}}}));
+
4420 },
+
4421 std::nullopt,
+
4422 0,
+
4423 std::nullopt,
+
4424 {features});
+
4425
+
4426 // Offer crossing with AMM and another offer. AMM has a better
+
4427 // quality and is consumed first.
+
4428 {
+
4429 Env env(*this, features);
+
4430 fund(env, gw, {alice, carol, bob}, XRP(30'000), {USD(30'000)});
+
4431 env(offer(bob, XRP(100), USD(100.001)));
+
4432 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
+
4433 env(offer(carol, USD(100), XRP(100)));
+
4434 if (!features[fixAMMv1_1])
+
4435 {
+
4436 BEAST_EXPECT(ammAlice.expectBalances(
+
4437 XRPAmount{10'049'825'373},
+
4438 STAmount{USD, UINT64_C(10'049'92586949302), -11},
+
4439 ammAlice.tokens()));
+
4440 BEAST_EXPECT(expectOffers(
+
4441 env,
+
4442 bob,
+
4443 1,
+
4444 {{{XRPAmount{50'074'629},
+
4445 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
+
4446 }
+
4447 else
+
4448 {
+
4449 BEAST_EXPECT(ammAlice.expectBalances(
+
4450 XRPAmount{10'049'825'372},
+
4451 STAmount{USD, UINT64_C(10'049'92587049303), -11},
+
4452 ammAlice.tokens()));
+
4453 BEAST_EXPECT(expectOffers(
+
4454 env,
+
4455 bob,
+
4456 1,
+
4457 {{{XRPAmount{50'074'628},
+
4458 STAmount{USD, UINT64_C(50'07512950697), -11}}}}));
+
4459 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
4460 }
+
4461 }
+
4462
+
4463 // Individually frozen account
+
4464 testAMM(
+
4465 [&](AMM& ammAlice, Env& env) {
+
4466 env(trust(gw, carol["USD"](0), tfSetFreeze));
+
4467 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
4468 env.close();
+
4469 env(pay(alice, carol, USD(1)),
+
4470 path(~USD),
+
4471 sendmax(XRP(10)),
+
4472 txflags(tfNoRippleDirect | tfPartialPayment),
+
4473 ter(tesSUCCESS));
+
4474 },
+
4475 std::nullopt,
+
4476 0,
+
4477 std::nullopt,
+
4478 {features});
+
4479 }
+
4480
+
4481 void
+
4482 testAMMTokens()
+
4483 {
+
4484 testcase("AMM Tokens");
+
4485 using namespace jtx;
+
4486
+
4487 // Offer crossing with AMM LPTokens and XRP.
+
4488 testAMM([&](AMM& ammAlice, Env& env) {
+
4489 auto const baseFee = env.current()->fees().base.drops();
+
4490 auto const token1 = ammAlice.lptIssue();
+
4491 auto priceXRP = withdrawByTokens(
+
4492 STAmount{XRPAmount{10'000'000'000}},
+
4493 STAmount{token1, 10'000'000},
+
4494 STAmount{token1, 5'000'000},
+
4495 0);
+
4496 // Carol places an order to buy LPTokens
+
4497 env(offer(carol, STAmount{token1, 5'000'000}, priceXRP));
+
4498 // Alice places an order to sell LPTokens
+
4499 env(offer(alice, priceXRP, STAmount{token1, 5'000'000}));
+
4500 // Pool's LPTokens balance doesn't change
+
4501 BEAST_EXPECT(ammAlice.expectBalances(
+
4502 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
+
4503 // Carol is Liquidity Provider
+
4504 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{5'000'000}));
+
4505 BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
+
4506 // Carol votes
+
4507 ammAlice.vote(carol, 1'000);
+
4508 BEAST_EXPECT(ammAlice.expectTradingFee(500));
+
4509 ammAlice.vote(carol, 0);
+
4510 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
4511 // Carol bids
+
4512 env(ammAlice.bid({.account = carol, .bidMin = 100}));
+
4513 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4'999'900}));
+
4514 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{100}));
+
4515 BEAST_EXPECT(
+
4516 accountBalance(env, carol) ==
+
4517 std::to_string(22500000000 - 4 * baseFee));
+
4518 priceXRP = withdrawByTokens(
+
4519 STAmount{XRPAmount{10'000'000'000}},
+
4520 STAmount{token1, 9'999'900},
+
4521 STAmount{token1, 4'999'900},
+
4522 0);
+
4523 // Carol withdraws
+
4524 ammAlice.withdrawAll(carol, XRP(0));
+
4525 BEAST_EXPECT(
+
4526 accountBalance(env, carol) ==
+
4527 std::to_string(29999949999 - 5 * baseFee));
+
4528 BEAST_EXPECT(ammAlice.expectBalances(
+
4529 XRPAmount{10'000'000'000} - priceXRP,
+
4530 USD(10'000),
+
4531 IOUAmount{5'000'000}));
+
4532 BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
+
4533 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
4534 });
+
4535
+
4536 // Offer crossing with two AMM LPTokens.
+
4537 testAMM([&](AMM& ammAlice, Env& env) {
+
4538 ammAlice.deposit(carol, 1'000'000);
+
4539 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
+
4540 AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000));
+
4541 ammAlice1.deposit(carol, 1'000'000);
+
4542 auto const token1 = ammAlice.lptIssue();
+
4543 auto const token2 = ammAlice1.lptIssue();
+
4544 env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100}),
+
4545 txflags(tfPassive));
+
4546 env.close();
+
4547 BEAST_EXPECT(expectOffers(env, alice, 1));
+
4548 env(offer(carol, STAmount{token2, 100}, STAmount{token1, 100}));
+
4549 env.close();
+
4550 BEAST_EXPECT(
+
4551 expectLine(env, alice, STAmount{token1, 10'000'100}) &&
+
4552 expectLine(env, alice, STAmount{token2, 9'999'900}));
+
4553 BEAST_EXPECT(
+
4554 expectLine(env, carol, STAmount{token2, 1'000'100}) &&
+
4555 expectLine(env, carol, STAmount{token1, 999'900}));
+
4556 BEAST_EXPECT(
+
4557 expectOffers(env, alice, 0) && expectOffers(env, carol, 0));
+
4558 });
+
4559
+
4560 // LPs pay LPTokens directly. Must trust set because the trust line
+
4561 // is checked for the limit, which is 0 in the AMM auto-created
+
4562 // trust line.
+
4563 testAMM([&](AMM& ammAlice, Env& env) {
+
4564 auto const token1 = ammAlice.lptIssue();
+
4565 env.trust(STAmount{token1, 2'000'000}, carol);
+
4566 env.close();
+
4567 ammAlice.deposit(carol, 1'000'000);
+
4568 BEAST_EXPECT(
+
4569 ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
+
4570 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
+
4571 // Pool balance doesn't change, only tokens moved from
+
4572 // one line to another.
+
4573 env(pay(alice, carol, STAmount{token1, 100}));
+
4574 env.close();
+
4575 BEAST_EXPECT(
+
4576 // Alice initial token1 10,000,000 - 100
+
4577 ammAlice.expectLPTokens(alice, IOUAmount{9'999'900, 0}) &&
+
4578 // Carol initial token1 1,000,000 + 100
+
4579 ammAlice.expectLPTokens(carol, IOUAmount{1'000'100, 0}));
+
4580
+
4581 env.trust(STAmount{token1, 20'000'000}, alice);
+
4582 env.close();
+
4583 env(pay(carol, alice, STAmount{token1, 100}));
+
4584 env.close();
+
4585 // Back to the original balance
+
4586 BEAST_EXPECT(
+
4587 ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
+
4588 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
+
4589 });
+
4590 }
+
4591
+
4592 void
+
4593 testAmendment()
+
4594 {
+
4595 testcase("Amendment");
+
4596 using namespace jtx;
+
4597 FeatureBitset const all{supported_amendments()};
+
4598 FeatureBitset const noAMM{all - featureAMM};
+
4599 FeatureBitset const noNumber{all - fixUniversalNumber};
+
4600 FeatureBitset const noAMMAndNumber{
+
4601 all - featureAMM - fixUniversalNumber};
+
4602
+
4603 for (auto const& feature : {noAMM, noNumber, noAMMAndNumber})
+
4604 {
+
4605 Env env{*this, feature};
+
4606 fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
+
4607 AMM amm(env, alice, XRP(1'000), USD(1'000), ter(temDISABLED));
+
4608
+
4609 env(amm.bid({.bidMax = 1000}), ter(temMALFORMED));
+
4610 env(amm.bid({}), ter(temDISABLED));
+
4611 amm.vote(VoteArg{.tfee = 100, .err = ter(temDISABLED)});
+
4612 amm.withdraw(WithdrawArg{.tokens = 100, .err = ter(temMALFORMED)});
+
4613 amm.withdraw(WithdrawArg{.err = ter(temDISABLED)});
+
4614 amm.deposit(
+
4615 DepositArg{.asset1In = USD(100), .err = ter(temDISABLED)});
+
4616 amm.ammDelete(alice, ter(temDISABLED));
+
4617 }
+
4618 }
+
4619
+
4620 void
+
4621 testFlags()
+
4622 {
+
4623 testcase("Flags");
+
4624 using namespace jtx;
+
4625
+
4626 testAMM([&](AMM& ammAlice, Env& env) {
+
4627 auto const info = env.rpc(
+
4628 "json",
+
4629 "account_info",
+
4630 std::string(
+
4631 "{\"account\": \"" + to_string(ammAlice.ammAccount()) +
+
4632 "\"}"));
+
4633 auto const flags =
+
4634 info[jss::result][jss::account_data][jss::Flags].asUInt();
+
4635 BEAST_EXPECT(
+
4636 flags ==
+
4637 (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
+
4638 });
+
4639 }
+
4640
+
4641 void
+
4642 testRippling()
+
4643 {
+
4644 testcase("Rippling");
+
4645 using namespace jtx;
+
4646
+
4647 // Rippling via AMM fails because AMM trust line has 0 limit.
+
4648 // Set up two issuers, A and B. Have each issue a token called TST.
+
4649 // Have another account C hold TST from both issuers,
+
4650 // and create an AMM for this pair.
+
4651 // Have a fourth account, D, create a trust line to the AMM for TST.
+
4652 // Send a payment delivering TST.AMM from C to D, using SendMax in
+
4653 // TST.A (or B) and a path through the AMM account. By normal
+
4654 // rippling rules, this would have caused the AMM's balances
+
4655 // to shift at a 1:1 rate with no fee applied has it not been
+
4656 // for 0 limit.
+
4657 {
+
4658 Env env(*this);
+
4659 auto const A = Account("A");
+
4660 auto const B = Account("B");
+
4661 auto const TSTA = A["TST"];
+
4662 auto const TSTB = B["TST"];
+
4663 auto const C = Account("C");
+
4664 auto const D = Account("D");
+
4665
+
4666 env.fund(XRP(10'000), A);
+
4667 env.fund(XRP(10'000), B);
+
4668 env.fund(XRP(10'000), C);
+
4669 env.fund(XRP(10'000), D);
+
4670
+
4671 env.trust(TSTA(10'000), C);
+
4672 env.trust(TSTB(10'000), C);
+
4673 env(pay(A, C, TSTA(10'000)));
+
4674 env(pay(B, C, TSTB(10'000)));
+
4675 AMM amm(env, C, TSTA(5'000), TSTB(5'000));
+
4676 auto const ammIss = Issue(TSTA.currency, amm.ammAccount());
+
4677
+
4678 // Can SetTrust only for AMM LP tokens
+
4679 env(trust(D, STAmount{ammIss, 10'000}), ter(tecNO_PERMISSION));
+
4680 env.close();
+
4681
+
4682 // The payment would fail because of above, but check just in case
+
4683 env(pay(C, D, STAmount{ammIss, 10}),
+
4684 sendmax(TSTA(100)),
+
4685 path(amm.ammAccount()),
+
4686 txflags(tfPartialPayment | tfNoRippleDirect),
+
4687 ter(tecPATH_DRY));
+
4688 }
+
4689 }
+
4690
+
4691 void
+
4692 testAMMAndCLOB(FeatureBitset features)
+
4693 {
+
4694 testcase("AMMAndCLOB, offer quality change");
+
4695 using namespace jtx;
+
4696 auto const gw = Account("gw");
+
4697 auto const TST = gw["TST"];
+
4698 auto const LP1 = Account("LP1");
+
4699 auto const LP2 = Account("LP2");
+
4700
+
4701 auto prep = [&](auto const& offerCb, auto const& expectCb) {
+
4702 Env env(*this, features);
+
4703 env.fund(XRP(30'000'000'000), gw);
+
4704 env(offer(gw, XRP(11'500'000'000), TST(1'000'000'000)));
+
4705
+
4706 env.fund(XRP(10'000), LP1);
+
4707 env.fund(XRP(10'000), LP2);
+
4708 env(offer(LP1, TST(25), XRPAmount(287'500'000)));
+
4709
+
4710 // Either AMM or CLOB offer
+
4711 offerCb(env);
+
4712
+
4713 env(offer(LP2, TST(25), XRPAmount(287'500'000)));
+
4714
+
4715 expectCb(env);
+
4716 };
+
4717
+
4718 // If we replace AMM with an equivalent CLOB offer, which AMM generates
+
4719 // when it is consumed, then the result must be equivalent, too.
+
4720 std::string lp2TSTBalance;
+
4721 std::string lp2TakerGets;
+
4722 std::string lp2TakerPays;
+
4723 // Execute with AMM first
+
4724 prep(
+
4725 [&](Env& env) { AMM amm(env, LP1, TST(25), XRP(250)); },
+
4726 [&](Env& env) {
+
4727 lp2TSTBalance =
+
4728 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
+
4729 .asString();
+
4730 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
+
4731 lp2TakerGets = offer["taker_gets"].asString();
+
4732 lp2TakerPays = offer["taker_pays"]["value"].asString();
+
4733 });
+
4734 // Execute with CLOB offer
+
4735 prep(
+
4736 [&](Env& env) {
+
4737 if (!features[fixAMMv1_1])
+
4738 env(offer(
+
4739 LP1,
+
4740 XRPAmount{18'095'133},
+
4741 STAmount{TST, UINT64_C(1'68737984885388), -14}),
+
4742 txflags(tfPassive));
+
4743 else
+
4744 env(offer(
+
4745 LP1,
+
4746 XRPAmount{18'095'132},
+
4747 STAmount{TST, UINT64_C(1'68737976189735), -14}),
+
4748 txflags(tfPassive));
+
4749 },
+
4750 [&](Env& env) {
+
4751 BEAST_EXPECT(
+
4752 lp2TSTBalance ==
+
4753 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
+
4754 .asString());
+
4755 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
+
4756 BEAST_EXPECT(lp2TakerGets == offer["taker_gets"].asString());
+
4757 BEAST_EXPECT(
+
4758 lp2TakerPays == offer["taker_pays"]["value"].asString());
+
4759 });
+
4760 }
+
4761
+
4762 void
+
4763 testTradingFee(FeatureBitset features)
+
4764 {
+
4765 testcase("Trading Fee");
+
4766 using namespace jtx;
+
4767
+
4768 // Single Deposit, 1% fee
+
4769 testAMM(
+
4770 [&](AMM& ammAlice, Env& env) {
+
4771 // No fee
+
4772 ammAlice.deposit(carol, USD(3'000));
+
4773 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
+
4774 ammAlice.withdrawAll(carol, USD(3'000));
+
4775 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
4776 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
4777 // Set fee to 1%
+
4778 ammAlice.vote(alice, 1'000);
+
4779 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
+
4780 // Carol gets fewer LPToken ~994, because of the single deposit
+
4781 // fee
+
4782 ammAlice.deposit(carol, USD(3'000));
+
4783 BEAST_EXPECT(ammAlice.expectLPTokens(
+
4784 carol, IOUAmount{994'981155689671, -12}));
+
4785 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
+
4786 // Set fee to 0
+
4787 ammAlice.vote(alice, 0);
+
4788 ammAlice.withdrawAll(carol, USD(0));
+
4789 // Carol gets back less than the original deposit
+
4790 BEAST_EXPECT(expectLine(
+
4791 env,
+
4792 carol,
+
4793 STAmount{USD, UINT64_C(29'994'96220068281), -11}));
+
4794 },
+
4795 {{USD(1'000), EUR(1'000)}},
+
4796 0,
+
4797 std::nullopt,
+
4798 {features});
+
4799
+
4800 // Single deposit with EP not exceeding specified:
+
4801 // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut). 1% fee.
+
4802 testAMM(
+
4803 [&](AMM& ammAlice, Env& env) {
+
4804 auto const balance = env.balance(carol, USD);
+
4805 auto tokensFee = ammAlice.deposit(
+
4806 carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
+
4807 auto const deposit = balance - env.balance(carol, USD);
+
4808 ammAlice.withdrawAll(carol, USD(0));
+
4809 ammAlice.vote(alice, 0);
+
4810 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
4811 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
+
4812 // carol pays ~2008 LPTokens in fees or ~0.5% of the no-fee
+
4813 // LPTokens
+
4814 BEAST_EXPECT(tokensFee == IOUAmount(485'636'0611129, -7));
+
4815 BEAST_EXPECT(tokensNoFee == IOUAmount(487'644'85901109, -8));
+
4816 },
+
4817 std::nullopt,
+
4818 1'000,
+
4819 std::nullopt,
+
4820 {features});
+
4821
+
4822 // Single deposit with EP not exceeding specified:
+
4823 // 200USD with EP not to exceed 0.002020 (AssetIn/TokensOut). 1% fee
+
4824 testAMM(
+
4825 [&](AMM& ammAlice, Env& env) {
+
4826 auto const balance = env.balance(carol, USD);
+
4827 auto const tokensFee = ammAlice.deposit(
+
4828 carol, USD(200), std::nullopt, STAmount{USD, 2020, -6});
+
4829 auto const deposit = balance - env.balance(carol, USD);
+
4830 ammAlice.withdrawAll(carol, USD(0));
+
4831 ammAlice.vote(alice, 0);
+
4832 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
4833 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
+
4834 // carol pays ~475 LPTokens in fees or ~0.5% of the no-fee
+
4835 // LPTokens
+
4836 BEAST_EXPECT(tokensFee == IOUAmount(98'000'00000002, -8));
+
4837 BEAST_EXPECT(tokensNoFee == IOUAmount(98'475'81871545, -8));
+
4838 },
+
4839 std::nullopt,
+
4840 1'000,
+
4841 std::nullopt,
+
4842 {features});
+
4843
+
4844 // Single Withdrawal, 1% fee
+
4845 testAMM(
+
4846 [&](AMM& ammAlice, Env& env) {
+
4847 // No fee
+
4848 ammAlice.deposit(carol, USD(3'000));
+
4849
+
4850 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
+
4851 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
+
4852 // Set fee to 1%
+
4853 ammAlice.vote(alice, 1'000);
+
4854 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
+
4855 // Single withdrawal. Carol gets ~5USD less than deposited.
+
4856 ammAlice.withdrawAll(carol, USD(0));
+
4857 BEAST_EXPECT(expectLine(
+
4858 env,
+
4859 carol,
+
4860 STAmount{USD, UINT64_C(29'994'97487437186), -11}));
+
4861 },
+
4862 {{USD(1'000), EUR(1'000)}},
+
4863 0,
+
4864 std::nullopt,
+
4865 {features});
+
4866
+
4867 // Withdraw with EPrice limit, 1% fee.
+
4868 testAMM(
+
4869 [&](AMM& ammAlice, Env& env) {
+
4870 ammAlice.deposit(carol, 1'000'000);
+
4871 auto const tokensFee = ammAlice.withdraw(
+
4872 carol, USD(100), std::nullopt, IOUAmount{520, 0});
+
4873 // carol withdraws ~1,443.44USD
+
4874 auto const balanceAfterWithdraw = [&]() {
+
4875 if (!features[fixAMMv1_1])
+
4876 return STAmount(USD, UINT64_C(30'443'43891402715), -11);
+
4877 return STAmount(USD, UINT64_C(30'443'43891402714), -11);
+
4878 }();
+
4879 BEAST_EXPECT(env.balance(carol, USD) == balanceAfterWithdraw);
+
4880 // Set to original pool size
+
4881 auto const deposit = balanceAfterWithdraw - USD(29'000);
+
4882 ammAlice.deposit(carol, deposit);
+
4883 // fee 0%
+
4884 ammAlice.vote(alice, 0);
+
4885 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
4886 auto const tokensNoFee = ammAlice.withdraw(carol, deposit);
+
4887 if (!features[fixAMMv1_1])
+
4888 BEAST_EXPECT(
+
4889 env.balance(carol, USD) ==
+
4890 STAmount(USD, UINT64_C(30'443'43891402717), -11));
+
4891 else
+
4892 BEAST_EXPECT(
+
4893 env.balance(carol, USD) ==
+
4894 STAmount(USD, UINT64_C(30'443'43891402716), -11));
+
4895 // carol pays ~4008 LPTokens in fees or ~0.5% of the no-fee
+
4896 // LPTokens
+
4897 if (!features[fixAMMv1_1])
+
4898 BEAST_EXPECT(
+
4899 tokensNoFee == IOUAmount(746'579'80779913, -8));
+
4900 else
+
4901 BEAST_EXPECT(
+
4902 tokensNoFee == IOUAmount(746'579'80779912, -8));
+
4903 BEAST_EXPECT(tokensFee == IOUAmount(750'588'23529411, -8));
+
4904 },
+
4905 std::nullopt,
+
4906 1'000,
+
4907 std::nullopt,
+
4908 {features});
+
4909
+
4910 // Payment, 1% fee
+
4911 testAMM(
+
4912 [&](AMM& ammAlice, Env& env) {
+
4913 fund(
+
4914 env,
+
4915 gw,
+
4916 {bob},
+
4917 XRP(1'000),
+
4918 {USD(1'000), EUR(1'000)},
+
4919 Fund::Acct);
+
4920 // Alice contributed 1010EUR and 1000USD to the pool
+
4921 BEAST_EXPECT(expectLine(env, alice, EUR(28'990)));
+
4922 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
+
4923 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
4924 // Carol pays to Alice with no fee
+
4925 env(pay(carol, alice, EUR(10)),
+
4926 path(~EUR),
+
4927 sendmax(USD(10)),
+
4928 txflags(tfNoRippleDirect));
+
4929 env.close();
+
4930 // Alice has 10EUR more and Carol has 10USD less
+
4931 BEAST_EXPECT(expectLine(env, alice, EUR(29'000)));
+
4932 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
+
4933 BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
+
4934
+
4935 // Set fee to 1%
+
4936 ammAlice.vote(alice, 1'000);
+
4937 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
+
4938 // Bob pays to Carol with 1% fee
+
4939 env(pay(bob, carol, USD(10)),
+
4940 path(~USD),
+
4941 sendmax(EUR(15)),
+
4942 txflags(tfNoRippleDirect));
+
4943 env.close();
+
4944 // Bob sends 10.1~EUR to pay 10USD
+
4945 BEAST_EXPECT(expectLine(
+
4946 env, bob, STAmount{EUR, UINT64_C(989'8989898989899), -13}));
+
4947 // Carol got 10USD
+
4948 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
4949 BEAST_EXPECT(ammAlice.expectBalances(
+
4950 USD(1'000),
+
4951 STAmount{EUR, UINT64_C(1'010'10101010101), -11},
+
4952 ammAlice.tokens()));
+
4953 },
+
4954 {{USD(1'000), EUR(1'010)}},
+
4955 0,
+
4956 std::nullopt,
+
4957 {features});
+
4958
+
4959 // Offer crossing, 0.5% fee
+
4960 testAMM(
+
4961 [&](AMM& ammAlice, Env& env) {
+
4962 // No fee
+
4963 env(offer(carol, EUR(10), USD(10)));
+
4964 env.close();
+
4965 BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
+
4966 BEAST_EXPECT(expectLine(env, carol, EUR(30'010)));
+
4967 // Change pool composition back
+
4968 env(offer(carol, USD(10), EUR(10)));
+
4969 env.close();
+
4970 // Set fee to 0.5%
+
4971 ammAlice.vote(alice, 500);
+
4972 BEAST_EXPECT(ammAlice.expectTradingFee(500));
+
4973 env(offer(carol, EUR(10), USD(10)));
+
4974 env.close();
+
4975 // Alice gets fewer ~4.97EUR for ~5.02USD, the difference goes
+
4976 // to the pool
+
4977 BEAST_EXPECT(expectLine(
+
4978 env,
+
4979 carol,
+
4980 STAmount{USD, UINT64_C(29'995'02512562814), -11}));
+
4981 BEAST_EXPECT(expectLine(
+
4982 env,
+
4983 carol,
+
4984 STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
+
4985 BEAST_EXPECT(expectOffers(
+
4986 env,
+
4987 carol,
+
4988 1,
+
4989 {{Amounts{
+
4990 STAmount{EUR, UINT64_C(5'025125628140703), -15},
+
4991 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
+
4992 if (!features[fixAMMv1_1])
+
4993 {
+
4994 BEAST_EXPECT(ammAlice.expectBalances(
+
4995 STAmount{USD, UINT64_C(1'004'974874371859), -12},
+
4996 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
+
4997 ammAlice.tokens()));
+
4998 }
+
4999 else
+
5000 {
+
5001 BEAST_EXPECT(ammAlice.expectBalances(
+
5002 STAmount{USD, UINT64_C(1'004'97487437186), -11},
+
5003 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
+
5004 ammAlice.tokens()));
+
5005 }
+
5006 },
+
5007 {{USD(1'000), EUR(1'010)}},
+
5008 0,
+
5009 std::nullopt,
+
5010 {features});
+
5011
+
5012 // Payment with AMM and CLOB offer, 0 fee
+
5013 // AMM liquidity is consumed first up to CLOB offer quality
+
5014 // CLOB offer is fully consumed next
+
5015 // Remaining amount is consumed via AMM liquidity
+
5016 {
+
5017 Env env(*this, features);
+
5018 Account const ed("ed");
+
5019 fund(
+
5020 env,
+
5021 gw,
+
5022 {alice, bob, carol, ed},
+
5023 XRP(1'000),
+
5024 {USD(2'000), EUR(2'000)});
+
5025 env(offer(carol, EUR(5), USD(5)));
+
5026 AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
+
5027 env(pay(bob, ed, USD(10)),
+
5028 path(~USD),
+
5029 sendmax(EUR(15)),
+
5030 txflags(tfNoRippleDirect));
+
5031 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
+
5032 if (!features[fixAMMv1_1])
+
5033 {
+
5034 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
+
5035 BEAST_EXPECT(ammAlice.expectBalances(
+
5036 USD(1'000), EUR(1'005), ammAlice.tokens()));
+
5037 }
+
5038 else
+
5039 {
+
5040 BEAST_EXPECT(expectLine(
+
5041 env, bob, STAmount(EUR, UINT64_C(1989'999999999999), -12)));
+
5042 BEAST_EXPECT(ammAlice.expectBalances(
+
5043 USD(1'000),
+
5044 STAmount(EUR, UINT64_C(1005'000000000001), -12),
+
5045 ammAlice.tokens()));
+
5046 }
+
5047 BEAST_EXPECT(expectOffers(env, carol, 0));
+
5048 }
+
5049
+
5050 // Payment with AMM and CLOB offer. Same as above but with 0.25%
+
5051 // fee.
+
5052 {
+
5053 Env env(*this, features);
+
5054 Account const ed("ed");
+
5055 fund(
+
5056 env,
+
5057 gw,
+
5058 {alice, bob, carol, ed},
+
5059 XRP(1'000),
+
5060 {USD(2'000), EUR(2'000)});
+
5061 env(offer(carol, EUR(5), USD(5)));
+
5062 // Set 0.25% fee
+
5063 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 250);
+
5064 env(pay(bob, ed, USD(10)),
+
5065 path(~USD),
+
5066 sendmax(EUR(15)),
+
5067 txflags(tfNoRippleDirect));
+
5068 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
+
5069 if (!features[fixAMMv1_1])
+
5070 {
+
5071 BEAST_EXPECT(expectLine(
+
5072 env,
+
5073 bob,
+
5074 STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
+
5075 BEAST_EXPECT(ammAlice.expectBalances(
+
5076 USD(1'000),
+
5077 STAmount{EUR, UINT64_C(1'005'012546992382), -12},
+
5078 ammAlice.tokens()));
+
5079 }
+
5080 else
+
5081 {
+
5082 BEAST_EXPECT(expectLine(
+
5083 env,
+
5084 bob,
+
5085 STAmount{EUR, UINT64_C(1'989'987453007628), -12}));
+
5086 BEAST_EXPECT(ammAlice.expectBalances(
+
5087 USD(1'000),
+
5088 STAmount{EUR, UINT64_C(1'005'012546992372), -12},
+
5089 ammAlice.tokens()));
+
5090 }
+
5091 BEAST_EXPECT(expectOffers(env, carol, 0));
+
5092 }
+
5093
+
5094 // Payment with AMM and CLOB offer. AMM has a better
+
5095 // spot price quality, but 1% fee offsets that. As the result
+
5096 // the entire trade is executed via LOB.
+
5097 {
+
5098 Env env(*this, features);
+
5099 Account const ed("ed");
+
5100 fund(
+
5101 env,
+
5102 gw,
+
5103 {alice, bob, carol, ed},
+
5104 XRP(1'000),
+
5105 {USD(2'000), EUR(2'000)});
+
5106 env(offer(carol, EUR(10), USD(10)));
+
5107 // Set 1% fee
+
5108 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
+
5109 env(pay(bob, ed, USD(10)),
+
5110 path(~USD),
+
5111 sendmax(EUR(15)),
+
5112 txflags(tfNoRippleDirect));
+
5113 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
+
5114 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
+
5115 BEAST_EXPECT(ammAlice.expectBalances(
+
5116 USD(1'005), EUR(1'000), ammAlice.tokens()));
+
5117 BEAST_EXPECT(expectOffers(env, carol, 0));
+
5118 }
+
5119
+
5120 // Payment with AMM and CLOB offer. AMM has a better
+
5121 // spot price quality, but 1% fee offsets that.
+
5122 // The CLOB offer is consumed first and the remaining
+
5123 // amount is consumed via AMM liquidity.
+
5124 {
+
5125 Env env(*this, features);
+
5126 Account const ed("ed");
+
5127 fund(
+
5128 env,
+
5129 gw,
+
5130 {alice, bob, carol, ed},
+
5131 XRP(1'000),
+
5132 {USD(2'000), EUR(2'000)});
+
5133 env(offer(carol, EUR(9), USD(9)));
+
5134 // Set 1% fee
+
5135 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
+
5136 env(pay(bob, ed, USD(10)),
+
5137 path(~USD),
+
5138 sendmax(EUR(15)),
+
5139 txflags(tfNoRippleDirect));
+
5140 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
+
5141 BEAST_EXPECT(expectLine(
+
5142 env, bob, STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
+
5143 BEAST_EXPECT(ammAlice.expectBalances(
+
5144 USD(1'004),
+
5145 STAmount{EUR, UINT64_C(1'001'006076703288), -12},
+
5146 ammAlice.tokens()));
+
5147 BEAST_EXPECT(expectOffers(env, carol, 0));
+
5148 }
+
5149 }
+
5150
+
5151 void
+
5152 testAdjustedTokens(FeatureBitset features)
+
5153 {
+
5154 testcase("Adjusted Deposit/Withdraw Tokens");
+
5155
+
5156 using namespace jtx;
+
5157
+
5158 // Deposit/Withdraw in USD
+
5159 testAMM(
+
5160 [&](AMM& ammAlice, Env& env) {
+
5161 Account const bob("bob");
+
5162 Account const ed("ed");
+
5163 Account const paul("paul");
+
5164 Account const dan("dan");
+
5165 Account const chris("chris");
+
5166 Account const simon("simon");
+
5167 Account const ben("ben");
+
5168 Account const nataly("nataly");
+
5169 fund(
+
5170 env,
+
5171 gw,
+
5172 {bob, ed, paul, dan, chris, simon, ben, nataly},
+
5173 {USD(1'500'000)},
+
5174 Fund::Acct);
+
5175 for (int i = 0; i < 10; ++i)
+
5176 {
+
5177 ammAlice.deposit(ben, STAmount{USD, 1, -10});
+
5178 ammAlice.withdrawAll(ben, USD(0));
+
5179 ammAlice.deposit(simon, USD(0.1));
+
5180 ammAlice.withdrawAll(simon, USD(0));
+
5181 ammAlice.deposit(chris, USD(1));
+
5182 ammAlice.withdrawAll(chris, USD(0));
+
5183 ammAlice.deposit(dan, USD(10));
+
5184 ammAlice.withdrawAll(dan, USD(0));
+
5185 ammAlice.deposit(bob, USD(100));
+
5186 ammAlice.withdrawAll(bob, USD(0));
+
5187 ammAlice.deposit(carol, USD(1'000));
+
5188 ammAlice.withdrawAll(carol, USD(0));
+
5189 ammAlice.deposit(ed, USD(10'000));
+
5190 ammAlice.withdrawAll(ed, USD(0));
+
5191 ammAlice.deposit(paul, USD(100'000));
+
5192 ammAlice.withdrawAll(paul, USD(0));
+
5193 ammAlice.deposit(nataly, USD(1'000'000));
+
5194 ammAlice.withdrawAll(nataly, USD(0));
+
5195 }
+
5196 // Due to round off some accounts have a tiny gain, while
+
5197 // other have a tiny loss. The last account to withdraw
+
5198 // gets everything in the pool.
+
5199 if (!features[fixAMMv1_1])
+
5200 BEAST_EXPECT(ammAlice.expectBalances(
+
5201 XRP(10'000),
+
5202 STAmount{USD, UINT64_C(10'000'0000000013), -10},
+
5203 IOUAmount{10'000'000}));
+
5204 else
+
5205 BEAST_EXPECT(ammAlice.expectBalances(
+
5206 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
+
5207 BEAST_EXPECT(expectLine(env, ben, USD(1'500'000)));
+
5208 BEAST_EXPECT(expectLine(env, simon, USD(1'500'000)));
+
5209 BEAST_EXPECT(expectLine(env, chris, USD(1'500'000)));
+
5210 BEAST_EXPECT(expectLine(env, dan, USD(1'500'000)));
+
5211 if (!features[fixAMMv1_1])
+
5212 BEAST_EXPECT(expectLine(
+
5213 env,
+
5214 carol,
+
5215 STAmount{USD, UINT64_C(30'000'00000000001), -11}));
+
5216 else
+
5217 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
5218 BEAST_EXPECT(expectLine(env, ed, USD(1'500'000)));
+
5219 BEAST_EXPECT(expectLine(env, paul, USD(1'500'000)));
+
5220 if (!features[fixAMMv1_1])
+
5221 BEAST_EXPECT(expectLine(
+
5222 env,
+
5223 nataly,
+
5224 STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
+
5225 else
+
5226 BEAST_EXPECT(expectLine(
+
5227 env,
+
5228 nataly,
+
5229 STAmount{USD, UINT64_C(1'500'000'000000005), -9}));
+
5230 ammAlice.withdrawAll(alice);
+
5231 BEAST_EXPECT(!ammAlice.ammExists());
+
5232 if (!features[fixAMMv1_1])
+
5233 BEAST_EXPECT(expectLine(
+
5234 env,
+
5235 alice,
+
5236 STAmount{USD, UINT64_C(30'000'0000000013), -10}));
+
5237 else
+
5238 BEAST_EXPECT(expectLine(env, alice, USD(30'000)));
+
5239 // alice XRP balance is 30,000initial - 50 ammcreate fee -
+
5240 // 10drops fee
+
5241 BEAST_EXPECT(
+
5242 accountBalance(env, alice) ==
+
5243 std::to_string(
+
5244 29950000000 - env.current()->fees().base.drops()));
+
5245 },
+
5246 std::nullopt,
+
5247 0,
+
5248 std::nullopt,
+
5249 {features});
+
5250
+
5251 // Same as above but deposit/withdraw in XRP
+
5252 testAMM([&](AMM& ammAlice, Env& env) {
+
5253 Account const bob("bob");
+
5254 Account const ed("ed");
+
5255 Account const paul("paul");
+
5256 Account const dan("dan");
+
5257 Account const chris("chris");
+
5258 Account const simon("simon");
+
5259 Account const ben("ben");
+
5260 Account const nataly("nataly");
+
5261 fund(
+
5262 env,
+
5263 gw,
+
5264 {bob, ed, paul, dan, chris, simon, ben, nataly},
+
5265 XRP(2'000'000),
+
5266 {},
+
5267 Fund::Acct);
+
5268 for (int i = 0; i < 10; ++i)
+
5269 {
+
5270 ammAlice.deposit(ben, XRPAmount{1});
+
5271 ammAlice.withdrawAll(ben, XRP(0));
+
5272 ammAlice.deposit(simon, XRPAmount(1'000));
+
5273 ammAlice.withdrawAll(simon, XRP(0));
+
5274 ammAlice.deposit(chris, XRP(1));
+
5275 ammAlice.withdrawAll(chris, XRP(0));
+
5276 ammAlice.deposit(dan, XRP(10));
+
5277 ammAlice.withdrawAll(dan, XRP(0));
+
5278 ammAlice.deposit(bob, XRP(100));
+
5279 ammAlice.withdrawAll(bob, XRP(0));
+
5280 ammAlice.deposit(carol, XRP(1'000));
+
5281 ammAlice.withdrawAll(carol, XRP(0));
+
5282 ammAlice.deposit(ed, XRP(10'000));
+
5283 ammAlice.withdrawAll(ed, XRP(0));
+
5284 ammAlice.deposit(paul, XRP(100'000));
+
5285 ammAlice.withdrawAll(paul, XRP(0));
+
5286 ammAlice.deposit(nataly, XRP(1'000'000));
+
5287 ammAlice.withdrawAll(nataly, XRP(0));
+
5288 }
+
5289 // No round off with XRP in this test
+
5290 BEAST_EXPECT(ammAlice.expectBalances(
+
5291 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
+
5292 ammAlice.withdrawAll(alice);
+
5293 BEAST_EXPECT(!ammAlice.ammExists());
+
5294 // 20,000 initial - (deposit+withdraw) * 10
+
5295 auto const xrpBalance = (XRP(2'000'000) - txfee(env, 20)).getText();
+
5296 BEAST_EXPECT(accountBalance(env, ben) == xrpBalance);
+
5297 BEAST_EXPECT(accountBalance(env, simon) == xrpBalance);
+
5298 BEAST_EXPECT(accountBalance(env, chris) == xrpBalance);
+
5299 BEAST_EXPECT(accountBalance(env, dan) == xrpBalance);
+
5300
+
5301 auto const baseFee = env.current()->fees().base.drops();
+
5302 // 30,000 initial - (deposit+withdraw) * 10
+
5303 BEAST_EXPECT(
+
5304 accountBalance(env, carol) ==
+
5305 std::to_string(30000000000 - 20 * baseFee));
+
5306 BEAST_EXPECT(accountBalance(env, ed) == xrpBalance);
+
5307 BEAST_EXPECT(accountBalance(env, paul) == xrpBalance);
+
5308 BEAST_EXPECT(accountBalance(env, nataly) == xrpBalance);
+
5309 // 30,000 initial - 50 ammcreate fee - 10drops withdraw fee
+
5310 BEAST_EXPECT(
+
5311 accountBalance(env, alice) ==
+
5312 std::to_string(29950000000 - baseFee));
+
5313 });
+
5314 }
+
5315
+
5316 void
+
5317 testAutoDelete()
+
5318 {
+
5319 testcase("Auto Delete");
+
5320
+
5321 using namespace jtx;
+
5322 FeatureBitset const all{supported_amendments()};
+
5323
+
5324 {
+
5325 Env env(
+
5326 *this,
+
5327 envconfig([](std::unique_ptr<Config> cfg) {
+
5328 cfg->FEES.reference_fee = XRPAmount(1);
+
5329 return cfg;
+
5330 }),
+
5331 all);
+
5332 fund(env, gw, {alice}, XRP(20'000), {USD(10'000)});
+
5333 AMM amm(env, gw, XRP(10'000), USD(10'000));
+
5334 for (auto i = 0; i < maxDeletableAMMTrustLines + 10; ++i)
+
5335 {
+
5336 Account const a{std::to_string(i)};
+
5337 env.fund(XRP(1'000), a);
+
5338 env(trust(a, STAmount{amm.lptIssue(), 10'000}));
+
5339 env.close();
+
5340 }
+
5341 // The trustlines are partially deleted,
+
5342 // AMM is set to an empty state.
+
5343 amm.withdrawAll(gw);
+
5344 BEAST_EXPECT(amm.ammExists());
+
5345
+
5346 // Bid,Vote,Deposit,Withdraw,SetTrust failing with
+
5347 // tecAMM_EMPTY. Deposit succeeds with tfTwoAssetIfEmpty option.
+
5348 env(amm.bid({
+
5349 .account = alice,
+
5350 .bidMin = 1000,
+
5351 }),
+
5352 ter(tecAMM_EMPTY));
+
5353 amm.vote(
+
5354 std::nullopt,
+
5355 100,
5356 std::nullopt,
5357 std::nullopt,
-
5358 ter(tecAMM_EMPTY));
-
5359 amm.withdraw(
-
5360 alice, 100, std::nullopt, std::nullopt, ter(tecAMM_EMPTY));
-
5361 amm.deposit(
-
5362 alice,
-
5363 USD(100),
-
5364 std::nullopt,
+
5358 std::nullopt,
+
5359 ter(tecAMM_EMPTY));
+
5360 amm.withdraw(
+
5361 alice, 100, std::nullopt, std::nullopt, ter(tecAMM_EMPTY));
+
5362 amm.deposit(
+
5363 alice,
+
5364 USD(100),
5365 std::nullopt,
5366 std::nullopt,
-
5367 ter(tecAMM_EMPTY));
-
5368 env(trust(alice, STAmount{amm.lptIssue(), 10'000}),
-
5369 ter(tecAMM_EMPTY));
-
5370
-
5371 // Can deposit with tfTwoAssetIfEmpty option
-
5372 amm.deposit(
-
5373 alice,
-
5374 std::nullopt,
-
5375 XRP(10'000),
-
5376 USD(10'000),
-
5377 std::nullopt,
-
5378 tfTwoAssetIfEmpty,
-
5379 std::nullopt,
+
5367 std::nullopt,
+
5368 ter(tecAMM_EMPTY));
+
5369 env(trust(alice, STAmount{amm.lptIssue(), 10'000}),
+
5370 ter(tecAMM_EMPTY));
+
5371
+
5372 // Can deposit with tfTwoAssetIfEmpty option
+
5373 amm.deposit(
+
5374 alice,
+
5375 std::nullopt,
+
5376 XRP(10'000),
+
5377 USD(10'000),
+
5378 std::nullopt,
+
5379 tfTwoAssetIfEmpty,
5380 std::nullopt,
-
5381 1'000);
-
5382 BEAST_EXPECT(
-
5383 amm.expectBalances(XRP(10'000), USD(10'000), amm.tokens()));
-
5384 BEAST_EXPECT(amm.expectTradingFee(1'000));
-
5385 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
-
5386
-
5387 // Withdrawing all tokens deletes AMM since the number
-
5388 // of remaining trustlines is less than max
-
5389 amm.withdrawAll(alice);
-
5390 BEAST_EXPECT(!amm.ammExists());
-
5391 BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount())));
-
5392 }
-
5393
-
5394 {
-
5395 Env env(
-
5396 *this,
-
5397 envconfig([](std::unique_ptr<Config> cfg) {
-
5398 cfg->FEES.reference_fee = XRPAmount(1);
-
5399 return cfg;
-
5400 }),
-
5401 all);
-
5402 fund(env, gw, {alice}, XRP(20'000), {USD(10'000)});
-
5403 AMM amm(env, gw, XRP(10'000), USD(10'000));
-
5404 for (auto i = 0; i < maxDeletableAMMTrustLines * 2 + 10; ++i)
-
5405 {
-
5406 Account const a{std::to_string(i)};
-
5407 env.fund(XRP(1'000), a);
-
5408 env(trust(a, STAmount{amm.lptIssue(), 10'000}));
-
5409 env.close();
-
5410 }
-
5411 // The trustlines are partially deleted.
-
5412 amm.withdrawAll(gw);
-
5413 BEAST_EXPECT(amm.ammExists());
-
5414
-
5415 // AMMDelete has to be called twice to delete AMM.
-
5416 amm.ammDelete(alice, ter(tecINCOMPLETE));
-
5417 BEAST_EXPECT(amm.ammExists());
-
5418 // Deletes remaining trustlines and deletes AMM.
-
5419 amm.ammDelete(alice);
-
5420 BEAST_EXPECT(!amm.ammExists());
-
5421 BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount())));
-
5422
-
5423 // Try redundant delete
-
5424 amm.ammDelete(alice, ter(terNO_AMM));
-
5425 }
-
5426 }
-
5427
-
5428 void
-
5429 testClawback()
-
5430 {
-
5431 testcase("Clawback");
-
5432 using namespace jtx;
-
5433 Env env(*this);
-
5434 env.fund(XRP(2'000), gw);
-
5435 env.fund(XRP(2'000), alice);
-
5436 AMM amm(env, gw, XRP(1'000), USD(1'000));
-
5437 env(fset(gw, asfAllowTrustLineClawback), ter(tecOWNERS));
-
5438 }
-
5439
-
5440 void
-
5441 testAMMID()
-
5442 {
-
5443 testcase("AMMID");
-
5444 using namespace jtx;
-
5445 testAMM([&](AMM& amm, Env& env) {
-
5446 amm.setClose(false);
-
5447 auto const info = env.rpc(
-
5448 "json",
-
5449 "account_info",
-
5450 std::string(
-
5451 "{\"account\": \"" + to_string(amm.ammAccount()) + "\"}"));
-
5452 try
-
5453 {
-
5454 BEAST_EXPECT(
-
5455 info[jss::result][jss::account_data][jss::AMMID]
-
5456 .asString() == to_string(amm.ammID()));
-
5457 }
-
5458 catch (...)
-
5459 {
-
5460 fail();
-
5461 }
-
5462 amm.deposit(carol, 1'000);
-
5463 auto affected = env.meta()->getJson(
-
5464 JsonOptions::none)[sfAffectedNodes.fieldName];
-
5465 try
-
5466 {
-
5467 bool found = false;
-
5468 for (auto const& node : affected)
-
5469 {
-
5470 if (node.isMember(sfModifiedNode.fieldName) &&
-
5471 node[sfModifiedNode.fieldName]
-
5472 [sfLedgerEntryType.fieldName]
-
5473 .asString() == "AccountRoot" &&
-
5474 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
-
5475 [jss::Account]
-
5476 .asString() == to_string(amm.ammAccount()))
-
5477 {
-
5478 found = node[sfModifiedNode.fieldName]
-
5479 [sfFinalFields.fieldName][jss::AMMID]
-
5480 .asString() == to_string(amm.ammID());
-
5481 break;
-
5482 }
-
5483 }
-
5484 BEAST_EXPECT(found);
-
5485 }
-
5486 catch (...)
-
5487 {
-
5488 fail();
-
5489 }
-
5490 });
-
5491 }
-
5492
-
5493 void
-
5494 testSelection(FeatureBitset features)
-
5495 {
-
5496 testcase("Offer/Strand Selection");
-
5497 using namespace jtx;
-
5498 Account const ed("ed");
-
5499 Account const gw1("gw1");
-
5500 auto const ETH = gw1["ETH"];
-
5501 auto const CAN = gw1["CAN"];
-
5502
-
5503 // These tests are expected to fail if the OwnerPaysFee feature
-
5504 // is ever supported. Updates will need to be made to AMM handling
-
5505 // in the payment engine, and these tests will need to be updated.
-
5506
-
5507 auto prep = [&](Env& env, auto gwRate, auto gw1Rate) {
-
5508 fund(env, gw, {alice, carol, bob, ed}, XRP(2'000), {USD(2'000)});
-
5509 env.fund(XRP(2'000), gw1);
-
5510 fund(
-
5511 env,
-
5512 gw1,
-
5513 {alice, carol, bob, ed},
-
5514 {ETH(2'000), CAN(2'000)},
-
5515 Fund::IOUOnly);
-
5516 env(rate(gw, gwRate));
-
5517 env(rate(gw1, gw1Rate));
-
5518 env.close();
-
5519 };
-
5520
-
5521 for (auto const& rates :
-
5522 {std::make_pair(1.5, 1.9), std::make_pair(1.9, 1.5)})
-
5523 {
-
5524 // Offer Selection
-
5525
-
5526 // Cross-currency payment: AMM has the same spot price quality
-
5527 // as CLOB's offer and can't generate a better quality offer.
-
5528 // The transfer fee in this case doesn't change the CLOB quality
-
5529 // because trIn is ignored on adjustment and trOut on payment is
-
5530 // also ignored because ownerPaysTransferFee is false in this
-
5531 // case. Run test for 0) offer, 1) AMM, 2) offer and AMM to
-
5532 // verify that the quality is better in the first case, and CLOB
-
5533 // is selected in the second case.
-
5534 {
-
5535 std::array<Quality, 3> q;
-
5536 for (auto i = 0; i < 3; ++i)
-
5537 {
-
5538 Env env(*this, features);
-
5539 prep(env, rates.first, rates.second);
-
5540 std::optional<AMM> amm;
-
5541 if (i == 0 || i == 2)
-
5542 {
-
5543 env(offer(ed, ETH(400), USD(400)), txflags(tfPassive));
-
5544 env.close();
-
5545 }
-
5546 if (i > 0)
-
5547 amm.emplace(env, ed, USD(1'000), ETH(1'000));
-
5548 env(pay(carol, bob, USD(100)),
-
5549 path(~USD),
-
5550 sendmax(ETH(500)));
-
5551 env.close();
-
5552 // CLOB and AMM, AMM is not selected
-
5553 if (i == 2)
-
5554 {
-
5555 BEAST_EXPECT(amm->expectBalances(
-
5556 USD(1'000), ETH(1'000), amm->tokens()));
-
5557 }
-
5558 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
-
5559 q[i] = Quality(Amounts{
-
5560 ETH(2'000) - env.balance(carol, ETH),
-
5561 env.balance(bob, USD) - USD(2'000)});
-
5562 }
-
5563 // CLOB is better quality than AMM
-
5564 BEAST_EXPECT(q[0] > q[1]);
-
5565 // AMM is not selected with CLOB
-
5566 BEAST_EXPECT(q[0] == q[2]);
-
5567 }
-
5568 // Offer crossing: AMM has the same spot price quality
-
5569 // as CLOB's offer and can't generate a better quality offer.
-
5570 // The transfer fee in this case doesn't change the CLOB quality
-
5571 // because the quality adjustment is ignored for the offer
-
5572 // crossing.
-
5573 for (auto i = 0; i < 3; ++i)
-
5574 {
-
5575 Env env(*this, features);
-
5576 prep(env, rates.first, rates.second);
-
5577 std::optional<AMM> amm;
-
5578 if (i == 0 || i == 2)
-
5579 {
-
5580 env(offer(ed, ETH(400), USD(400)), txflags(tfPassive));
-
5581 env.close();
-
5582 }
-
5583 if (i > 0)
-
5584 amm.emplace(env, ed, USD(1'000), ETH(1'000));
-
5585 env(offer(alice, USD(400), ETH(400)));
-
5586 env.close();
-
5587 // AMM is not selected
-
5588 if (i > 0)
-
5589 {
-
5590 BEAST_EXPECT(amm->expectBalances(
-
5591 USD(1'000), ETH(1'000), amm->tokens()));
-
5592 }
-
5593 if (i == 0 || i == 2)
-
5594 {
-
5595 // Fully crosses
-
5596 BEAST_EXPECT(expectOffers(env, alice, 0));
-
5597 }
-
5598 // Fails to cross because AMM is not selected
-
5599 else
-
5600 {
-
5601 BEAST_EXPECT(expectOffers(
-
5602 env, alice, 1, {Amounts{USD(400), ETH(400)}}));
-
5603 }
-
5604 BEAST_EXPECT(expectOffers(env, ed, 0));
-
5605 }
-
5606
-
5607 // Show that the CLOB quality reduction
-
5608 // results in AMM offer selection.
-
5609
-
5610 // Same as the payment but reduced offer quality
-
5611 {
-
5612 std::array<Quality, 3> q;
-
5613 for (auto i = 0; i < 3; ++i)
-
5614 {
-
5615 Env env(*this, features);
-
5616 prep(env, rates.first, rates.second);
-
5617 std::optional<AMM> amm;
-
5618 if (i == 0 || i == 2)
-
5619 {
-
5620 env(offer(ed, ETH(400), USD(300)), txflags(tfPassive));
-
5621 env.close();
-
5622 }
-
5623 if (i > 0)
-
5624 amm.emplace(env, ed, USD(1'000), ETH(1'000));
-
5625 env(pay(carol, bob, USD(100)),
-
5626 path(~USD),
-
5627 sendmax(ETH(500)));
-
5628 env.close();
-
5629 // AMM and CLOB are selected
-
5630 if (i > 0)
-
5631 {
-
5632 BEAST_EXPECT(!amm->expectBalances(
-
5633 USD(1'000), ETH(1'000), amm->tokens()));
-
5634 }
-
5635 if (i == 2 && !features[fixAMMv1_1])
-
5636 {
-
5637 if (rates.first == 1.5)
-
5638 {
-
5639 if (!features[fixAMMv1_1])
-
5640 BEAST_EXPECT(expectOffers(
-
5641 env,
-
5642 ed,
-
5643 1,
-
5644 {{Amounts{
-
5645 STAmount{
-
5646 ETH,
-
5647 UINT64_C(378'6327949540823),
-
5648 -13},
-
5649 STAmount{
-
5650 USD,
-
5651 UINT64_C(283'9745962155617),
-
5652 -13}}}}));
-
5653 else
-
5654 BEAST_EXPECT(expectOffers(
-
5655 env,
-
5656 ed,
-
5657 1,
-
5658 {{Amounts{
-
5659 STAmount{
-
5660 ETH,
-
5661 UINT64_C(378'6327949540813),
-
5662 -13},
-
5663 STAmount{
-
5664 USD,
-
5665 UINT64_C(283'974596215561),
-
5666 -12}}}}));
-
5667 }
-
5668 else
-
5669 {
-
5670 if (!features[fixAMMv1_1])
-
5671 BEAST_EXPECT(expectOffers(
-
5672 env,
-
5673 ed,
-
5674 1,
-
5675 {{Amounts{
-
5676 STAmount{
-
5677 ETH,
-
5678 UINT64_C(325'299461620749),
-
5679 -12},
-
5680 STAmount{
-
5681 USD,
-
5682 UINT64_C(243'9745962155617),
-
5683 -13}}}}));
-
5684 else
-
5685 BEAST_EXPECT(expectOffers(
-
5686 env,
-
5687 ed,
-
5688 1,
-
5689 {{Amounts{
-
5690 STAmount{
-
5691 ETH,
-
5692 UINT64_C(325'299461620748),
-
5693 -12},
-
5694 STAmount{
-
5695 USD,
-
5696 UINT64_C(243'974596215561),
-
5697 -12}}}}));
-
5698 }
-
5699 }
-
5700 else if (i == 2)
-
5701 {
-
5702 if (rates.first == 1.5)
-
5703 {
-
5704 BEAST_EXPECT(expectOffers(
-
5705 env,
-
5706 ed,
-
5707 1,
-
5708 {{Amounts{
-
5709 STAmount{
-
5710 ETH, UINT64_C(378'6327949540812), -13},
-
5711 STAmount{
-
5712 USD,
-
5713 UINT64_C(283'9745962155609),
-
5714 -13}}}}));
-
5715 }
-
5716 else
-
5717 {
-
5718 BEAST_EXPECT(expectOffers(
-
5719 env,
-
5720 ed,
-
5721 1,
-
5722 {{Amounts{
-
5723 STAmount{
-
5724 ETH, UINT64_C(325'2994616207479), -13},
-
5725 STAmount{
-
5726 USD,
-
5727 UINT64_C(243'9745962155609),
-
5728 -13}}}}));
-
5729 }
-
5730 }
-
5731 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
-
5732 q[i] = Quality(Amounts{
-
5733 ETH(2'000) - env.balance(carol, ETH),
-
5734 env.balance(bob, USD) - USD(2'000)});
-
5735 }
-
5736 // AMM is better quality
-
5737 BEAST_EXPECT(q[1] > q[0]);
-
5738 // AMM and CLOB produce better quality
-
5739 BEAST_EXPECT(q[2] > q[1]);
-
5740 }
-
5741
-
5742 // Same as the offer-crossing but reduced offer quality
-
5743 for (auto i = 0; i < 3; ++i)
-
5744 {
-
5745 Env env(*this, features);
-
5746 prep(env, rates.first, rates.second);
-
5747 std::optional<AMM> amm;
-
5748 if (i == 0 || i == 2)
-
5749 {
-
5750 env(offer(ed, ETH(400), USD(250)), txflags(tfPassive));
-
5751 env.close();
-
5752 }
-
5753 if (i > 0)
-
5754 amm.emplace(env, ed, USD(1'000), ETH(1'000));
-
5755 env(offer(alice, USD(250), ETH(400)));
-
5756 env.close();
-
5757 // AMM is selected in both cases
-
5758 if (i > 0)
-
5759 {
-
5760 BEAST_EXPECT(!amm->expectBalances(
-
5761 USD(1'000), ETH(1'000), amm->tokens()));
-
5762 }
-
5763 // Partially crosses, AMM is selected, CLOB fails
-
5764 // limitQuality
-
5765 if (i == 2)
-
5766 {
-
5767 if (rates.first == 1.5)
-
5768 {
-
5769 if (!features[fixAMMv1_1])
-
5770 {
-
5771 BEAST_EXPECT(expectOffers(
-
5772 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
-
5773 BEAST_EXPECT(expectOffers(
-
5774 env,
-
5775 alice,
-
5776 1,
-
5777 {{Amounts{
-
5778 STAmount{
-
5779 USD, UINT64_C(40'5694150420947), -13},
-
5780 STAmount{
-
5781 ETH, UINT64_C(64'91106406735152), -14},
-
5782 }}}));
-
5783 }
-
5784 else
-
5785 {
-
5786 // Ed offer is partially crossed.
-
5787 // The updated rounding makes limitQuality
-
5788 // work if both amendments are enabled
-
5789 BEAST_EXPECT(expectOffers(
-
5790 env,
-
5791 ed,
-
5792 1,
-
5793 {{Amounts{
-
5794 STAmount{
-
5795 ETH, UINT64_C(335'0889359326475), -13},
-
5796 STAmount{
-
5797 USD, UINT64_C(209'4305849579047), -13},
-
5798 }}}));
-
5799 BEAST_EXPECT(expectOffers(env, alice, 0));
-
5800 }
-
5801 }
-
5802 else
-
5803 {
-
5804 if (!features[fixAMMv1_1])
-
5805 {
-
5806 // Ed offer is partially crossed.
-
5807 BEAST_EXPECT(expectOffers(
-
5808 env,
-
5809 ed,
-
5810 1,
-
5811 {{Amounts{
-
5812 STAmount{
-
5813 ETH, UINT64_C(335'0889359326485), -13},
-
5814 STAmount{
-
5815 USD, UINT64_C(209'4305849579053), -13},
-
5816 }}}));
-
5817 BEAST_EXPECT(expectOffers(env, alice, 0));
-
5818 }
-
5819 else
-
5820 {
-
5821 // Ed offer is partially crossed.
-
5822 BEAST_EXPECT(expectOffers(
-
5823 env,
-
5824 ed,
-
5825 1,
-
5826 {{Amounts{
-
5827 STAmount{
-
5828 ETH, UINT64_C(335'0889359326475), -13},
-
5829 STAmount{
-
5830 USD, UINT64_C(209'4305849579047), -13},
-
5831 }}}));
-
5832 BEAST_EXPECT(expectOffers(env, alice, 0));
-
5833 }
-
5834 }
-
5835 }
-
5836 }
-
5837
-
5838 // Strand selection
-
5839
-
5840 // Two book steps strand quality is 1.
-
5841 // AMM strand's best quality is equal to AMM's spot price
-
5842 // quality, which is 1. Both strands (steps) are adjusted
-
5843 // for the transfer fee in qualityUpperBound. In case
-
5844 // of two strands, AMM offers have better quality and are
-
5845 // consumed first, remaining liquidity is generated by CLOB
-
5846 // offers. Liquidity from two strands is better in this case
-
5847 // than in case of one strand with two book steps. Liquidity
-
5848 // from one strand with AMM has better quality than either one
-
5849 // strand with two book steps or two strands. It may appear
-
5850 // unintuitive, but one strand with AMM is optimized and
-
5851 // generates one AMM offer, while in case of two strands,
-
5852 // multiple AMM offers are generated, which results in slightly
-
5853 // worse overall quality.
-
5854 {
-
5855 std::array<Quality, 3> q;
-
5856 for (auto i = 0; i < 3; ++i)
-
5857 {
-
5858 Env env(*this, features);
-
5859 prep(env, rates.first, rates.second);
-
5860 std::optional<AMM> amm;
-
5861
-
5862 if (i == 0 || i == 2)
-
5863 {
-
5864 env(offer(ed, ETH(400), CAN(400)), txflags(tfPassive));
-
5865 env(offer(ed, CAN(400), USD(400))), txflags(tfPassive);
-
5866 env.close();
-
5867 }
-
5868
-
5869 if (i > 0)
-
5870 amm.emplace(env, ed, ETH(1'000), USD(1'000));
-
5871
-
5872 env(pay(carol, bob, USD(100)),
-
5873 path(~USD),
-
5874 path(~CAN, ~USD),
-
5875 sendmax(ETH(600)));
-
5876 env.close();
-
5877
-
5878 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
-
5879
-
5880 if (i == 2 && !features[fixAMMv1_1])
-
5881 {
-
5882 if (rates.first == 1.5)
-
5883 {
-
5884 // Liquidity is consumed from AMM strand only
-
5885 BEAST_EXPECT(amm->expectBalances(
-
5886 STAmount{ETH, UINT64_C(1'176'66038955758), -11},
-
5887 USD(850),
-
5888 amm->tokens()));
-
5889 }
-
5890 else
-
5891 {
-
5892 BEAST_EXPECT(amm->expectBalances(
-
5893 STAmount{
-
5894 ETH, UINT64_C(1'179'540094339627), -12},
-
5895 STAmount{USD, UINT64_C(847'7880529867501), -13},
-
5896 amm->tokens()));
-
5897 BEAST_EXPECT(expectOffers(
-
5898 env,
-
5899 ed,
-
5900 2,
-
5901 {{Amounts{
-
5902 STAmount{
-
5903 ETH,
-
5904 UINT64_C(343'3179205198749),
-
5905 -13},
-
5906 STAmount{
-
5907 CAN,
-
5908 UINT64_C(343'3179205198749),
-
5909 -13},
-
5910 },
-
5911 Amounts{
-
5912 STAmount{
-
5913 CAN,
-
5914 UINT64_C(362'2119470132499),
-
5915 -13},
-
5916 STAmount{
-
5917 USD,
-
5918 UINT64_C(362'2119470132499),
-
5919 -13},
-
5920 }}}));
-
5921 }
-
5922 }
-
5923 else if (i == 2)
-
5924 {
-
5925 if (rates.first == 1.5)
-
5926 {
-
5927 // Liquidity is consumed from AMM strand only
-
5928 BEAST_EXPECT(amm->expectBalances(
-
5929 STAmount{
-
5930 ETH, UINT64_C(1'176'660389557593), -12},
-
5931 USD(850),
-
5932 amm->tokens()));
-
5933 }
-
5934 else
-
5935 {
-
5936 BEAST_EXPECT(amm->expectBalances(
-
5937 STAmount{ETH, UINT64_C(1'179'54009433964), -11},
-
5938 STAmount{USD, UINT64_C(847'7880529867501), -13},
-
5939 amm->tokens()));
-
5940 BEAST_EXPECT(expectOffers(
-
5941 env,
-
5942 ed,
-
5943 2,
-
5944 {{Amounts{
-
5945 STAmount{
-
5946 ETH,
-
5947 UINT64_C(343'3179205198749),
-
5948 -13},
-
5949 STAmount{
-
5950 CAN,
-
5951 UINT64_C(343'3179205198749),
-
5952 -13},
-
5953 },
-
5954 Amounts{
-
5955 STAmount{
-
5956 CAN,
-
5957 UINT64_C(362'2119470132499),
-
5958 -13},
-
5959 STAmount{
-
5960 USD,
-
5961 UINT64_C(362'2119470132499),
-
5962 -13},
-
5963 }}}));
-
5964 }
-
5965 }
-
5966 q[i] = Quality(Amounts{
-
5967 ETH(2'000) - env.balance(carol, ETH),
-
5968 env.balance(bob, USD) - USD(2'000)});
-
5969 }
-
5970 BEAST_EXPECT(q[1] > q[0]);
-
5971 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
-
5972 }
-
5973 }
-
5974 }
-
5975
-
5976 void
-
5977 testFixDefaultInnerObj()
-
5978 {
-
5979 testcase("Fix Default Inner Object");
-
5980 using namespace jtx;
-
5981 FeatureBitset const all{supported_amendments()};
-
5982
-
5983 auto test = [&](FeatureBitset features,
-
5984 TER const& err1,
-
5985 TER const& err2,
-
5986 TER const& err3,
-
5987 TER const& err4,
-
5988 std::uint16_t tfee,
-
5989 bool closeLedger,
-
5990 std::optional<std::uint16_t> extra = std::nullopt) {
-
5991 Env env(*this, features);
-
5992 fund(env, gw, {alice}, XRP(1'000), {USD(10)});
-
5993 AMM amm(
-
5994 env,
-
5995 gw,
-
5996 XRP(10),
-
5997 USD(10),
-
5998 {.tfee = tfee, .close = closeLedger});
-
5999 amm.deposit(alice, USD(10), XRP(10));
-
6000 amm.vote(VoteArg{.account = alice, .tfee = tfee, .err = ter(err1)});
-
6001 amm.withdraw(WithdrawArg{
-
6002 .account = gw, .asset1Out = USD(1), .err = ter(err2)});
-
6003 // with the amendment disabled and ledger not closed,
-
6004 // second vote succeeds if the first vote sets the trading fee
-
6005 // to non-zero; if the first vote sets the trading fee to >0 &&
-
6006 // <9 then the second withdraw succeeds if the second vote sets
-
6007 // the trading fee so that the discounted fee is non-zero
-
6008 amm.vote(VoteArg{.account = alice, .tfee = 20, .err = ter(err3)});
-
6009 amm.withdraw(WithdrawArg{
-
6010 .account = gw, .asset1Out = USD(2), .err = ter(err4)});
-
6011 };
-
6012
-
6013 // ledger is closed after each transaction, vote/withdraw don't fail
-
6014 // regardless whether the amendment is enabled or not
-
6015 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, true);
-
6016 test(
-
6017 all - fixInnerObjTemplate,
-
6018 tesSUCCESS,
+
5381 std::nullopt,
+
5382 1'000);
+
5383 BEAST_EXPECT(
+
5384 amm.expectBalances(XRP(10'000), USD(10'000), amm.tokens()));
+
5385 BEAST_EXPECT(amm.expectTradingFee(1'000));
+
5386 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
+
5387
+
5388 // Withdrawing all tokens deletes AMM since the number
+
5389 // of remaining trustlines is less than max
+
5390 amm.withdrawAll(alice);
+
5391 BEAST_EXPECT(!amm.ammExists());
+
5392 BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount())));
+
5393 }
+
5394
+
5395 {
+
5396 Env env(
+
5397 *this,
+
5398 envconfig([](std::unique_ptr<Config> cfg) {
+
5399 cfg->FEES.reference_fee = XRPAmount(1);
+
5400 return cfg;
+
5401 }),
+
5402 all);
+
5403 fund(env, gw, {alice}, XRP(20'000), {USD(10'000)});
+
5404 AMM amm(env, gw, XRP(10'000), USD(10'000));
+
5405 for (auto i = 0; i < maxDeletableAMMTrustLines * 2 + 10; ++i)
+
5406 {
+
5407 Account const a{std::to_string(i)};
+
5408 env.fund(XRP(1'000), a);
+
5409 env(trust(a, STAmount{amm.lptIssue(), 10'000}));
+
5410 env.close();
+
5411 }
+
5412 // The trustlines are partially deleted.
+
5413 amm.withdrawAll(gw);
+
5414 BEAST_EXPECT(amm.ammExists());
+
5415
+
5416 // AMMDelete has to be called twice to delete AMM.
+
5417 amm.ammDelete(alice, ter(tecINCOMPLETE));
+
5418 BEAST_EXPECT(amm.ammExists());
+
5419 // Deletes remaining trustlines and deletes AMM.
+
5420 amm.ammDelete(alice);
+
5421 BEAST_EXPECT(!amm.ammExists());
+
5422 BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount())));
+
5423
+
5424 // Try redundant delete
+
5425 amm.ammDelete(alice, ter(terNO_AMM));
+
5426 }
+
5427 }
+
5428
+
5429 void
+
5430 testClawback()
+
5431 {
+
5432 testcase("Clawback");
+
5433 using namespace jtx;
+
5434 Env env(*this);
+
5435 env.fund(XRP(2'000), gw);
+
5436 env.fund(XRP(2'000), alice);
+
5437 AMM amm(env, gw, XRP(1'000), USD(1'000));
+
5438 env(fset(gw, asfAllowTrustLineClawback), ter(tecOWNERS));
+
5439 }
+
5440
+
5441 void
+
5442 testAMMID()
+
5443 {
+
5444 testcase("AMMID");
+
5445 using namespace jtx;
+
5446 testAMM([&](AMM& amm, Env& env) {
+
5447 amm.setClose(false);
+
5448 auto const info = env.rpc(
+
5449 "json",
+
5450 "account_info",
+
5451 std::string(
+
5452 "{\"account\": \"" + to_string(amm.ammAccount()) + "\"}"));
+
5453 try
+
5454 {
+
5455 BEAST_EXPECT(
+
5456 info[jss::result][jss::account_data][jss::AMMID]
+
5457 .asString() == to_string(amm.ammID()));
+
5458 }
+
5459 catch (...)
+
5460 {
+
5461 fail();
+
5462 }
+
5463 amm.deposit(carol, 1'000);
+
5464 auto affected = env.meta()->getJson(
+
5465 JsonOptions::none)[sfAffectedNodes.fieldName];
+
5466 try
+
5467 {
+
5468 bool found = false;
+
5469 for (auto const& node : affected)
+
5470 {
+
5471 if (node.isMember(sfModifiedNode.fieldName) &&
+
5472 node[sfModifiedNode.fieldName]
+
5473 [sfLedgerEntryType.fieldName]
+
5474 .asString() == "AccountRoot" &&
+
5475 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
+
5476 [jss::Account]
+
5477 .asString() == to_string(amm.ammAccount()))
+
5478 {
+
5479 found = node[sfModifiedNode.fieldName]
+
5480 [sfFinalFields.fieldName][jss::AMMID]
+
5481 .asString() == to_string(amm.ammID());
+
5482 break;
+
5483 }
+
5484 }
+
5485 BEAST_EXPECT(found);
+
5486 }
+
5487 catch (...)
+
5488 {
+
5489 fail();
+
5490 }
+
5491 });
+
5492 }
+
5493
+
5494 void
+
5495 testSelection(FeatureBitset features)
+
5496 {
+
5497 testcase("Offer/Strand Selection");
+
5498 using namespace jtx;
+
5499 Account const ed("ed");
+
5500 Account const gw1("gw1");
+
5501 auto const ETH = gw1["ETH"];
+
5502 auto const CAN = gw1["CAN"];
+
5503
+
5504 // These tests are expected to fail if the OwnerPaysFee feature
+
5505 // is ever supported. Updates will need to be made to AMM handling
+
5506 // in the payment engine, and these tests will need to be updated.
+
5507
+
5508 auto prep = [&](Env& env, auto gwRate, auto gw1Rate) {
+
5509 fund(env, gw, {alice, carol, bob, ed}, XRP(2'000), {USD(2'000)});
+
5510 env.fund(XRP(2'000), gw1);
+
5511 fund(
+
5512 env,
+
5513 gw1,
+
5514 {alice, carol, bob, ed},
+
5515 {ETH(2'000), CAN(2'000)},
+
5516 Fund::IOUOnly);
+
5517 env(rate(gw, gwRate));
+
5518 env(rate(gw1, gw1Rate));
+
5519 env.close();
+
5520 };
+
5521
+
5522 for (auto const& rates :
+
5523 {std::make_pair(1.5, 1.9), std::make_pair(1.9, 1.5)})
+
5524 {
+
5525 // Offer Selection
+
5526
+
5527 // Cross-currency payment: AMM has the same spot price quality
+
5528 // as CLOB's offer and can't generate a better quality offer.
+
5529 // The transfer fee in this case doesn't change the CLOB quality
+
5530 // because trIn is ignored on adjustment and trOut on payment is
+
5531 // also ignored because ownerPaysTransferFee is false in this
+
5532 // case. Run test for 0) offer, 1) AMM, 2) offer and AMM to
+
5533 // verify that the quality is better in the first case, and CLOB
+
5534 // is selected in the second case.
+
5535 {
+
5536 std::array<Quality, 3> q;
+
5537 for (auto i = 0; i < 3; ++i)
+
5538 {
+
5539 Env env(*this, features);
+
5540 prep(env, rates.first, rates.second);
+
5541 std::optional<AMM> amm;
+
5542 if (i == 0 || i == 2)
+
5543 {
+
5544 env(offer(ed, ETH(400), USD(400)), txflags(tfPassive));
+
5545 env.close();
+
5546 }
+
5547 if (i > 0)
+
5548 amm.emplace(env, ed, USD(1'000), ETH(1'000));
+
5549 env(pay(carol, bob, USD(100)),
+
5550 path(~USD),
+
5551 sendmax(ETH(500)));
+
5552 env.close();
+
5553 // CLOB and AMM, AMM is not selected
+
5554 if (i == 2)
+
5555 {
+
5556 BEAST_EXPECT(amm->expectBalances(
+
5557 USD(1'000), ETH(1'000), amm->tokens()));
+
5558 }
+
5559 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
+
5560 q[i] = Quality(Amounts{
+
5561 ETH(2'000) - env.balance(carol, ETH),
+
5562 env.balance(bob, USD) - USD(2'000)});
+
5563 }
+
5564 // CLOB is better quality than AMM
+
5565 BEAST_EXPECT(q[0] > q[1]);
+
5566 // AMM is not selected with CLOB
+
5567 BEAST_EXPECT(q[0] == q[2]);
+
5568 }
+
5569 // Offer crossing: AMM has the same spot price quality
+
5570 // as CLOB's offer and can't generate a better quality offer.
+
5571 // The transfer fee in this case doesn't change the CLOB quality
+
5572 // because the quality adjustment is ignored for the offer
+
5573 // crossing.
+
5574 for (auto i = 0; i < 3; ++i)
+
5575 {
+
5576 Env env(*this, features);
+
5577 prep(env, rates.first, rates.second);
+
5578 std::optional<AMM> amm;
+
5579 if (i == 0 || i == 2)
+
5580 {
+
5581 env(offer(ed, ETH(400), USD(400)), txflags(tfPassive));
+
5582 env.close();
+
5583 }
+
5584 if (i > 0)
+
5585 amm.emplace(env, ed, USD(1'000), ETH(1'000));
+
5586 env(offer(alice, USD(400), ETH(400)));
+
5587 env.close();
+
5588 // AMM is not selected
+
5589 if (i > 0)
+
5590 {
+
5591 BEAST_EXPECT(amm->expectBalances(
+
5592 USD(1'000), ETH(1'000), amm->tokens()));
+
5593 }
+
5594 if (i == 0 || i == 2)
+
5595 {
+
5596 // Fully crosses
+
5597 BEAST_EXPECT(expectOffers(env, alice, 0));
+
5598 }
+
5599 // Fails to cross because AMM is not selected
+
5600 else
+
5601 {
+
5602 BEAST_EXPECT(expectOffers(
+
5603 env, alice, 1, {Amounts{USD(400), ETH(400)}}));
+
5604 }
+
5605 BEAST_EXPECT(expectOffers(env, ed, 0));
+
5606 }
+
5607
+
5608 // Show that the CLOB quality reduction
+
5609 // results in AMM offer selection.
+
5610
+
5611 // Same as the payment but reduced offer quality
+
5612 {
+
5613 std::array<Quality, 3> q;
+
5614 for (auto i = 0; i < 3; ++i)
+
5615 {
+
5616 Env env(*this, features);
+
5617 prep(env, rates.first, rates.second);
+
5618 std::optional<AMM> amm;
+
5619 if (i == 0 || i == 2)
+
5620 {
+
5621 env(offer(ed, ETH(400), USD(300)), txflags(tfPassive));
+
5622 env.close();
+
5623 }
+
5624 if (i > 0)
+
5625 amm.emplace(env, ed, USD(1'000), ETH(1'000));
+
5626 env(pay(carol, bob, USD(100)),
+
5627 path(~USD),
+
5628 sendmax(ETH(500)));
+
5629 env.close();
+
5630 // AMM and CLOB are selected
+
5631 if (i > 0)
+
5632 {
+
5633 BEAST_EXPECT(!amm->expectBalances(
+
5634 USD(1'000), ETH(1'000), amm->tokens()));
+
5635 }
+
5636 if (i == 2 && !features[fixAMMv1_1])
+
5637 {
+
5638 if (rates.first == 1.5)
+
5639 {
+
5640 if (!features[fixAMMv1_1])
+
5641 BEAST_EXPECT(expectOffers(
+
5642 env,
+
5643 ed,
+
5644 1,
+
5645 {{Amounts{
+
5646 STAmount{
+
5647 ETH,
+
5648 UINT64_C(378'6327949540823),
+
5649 -13},
+
5650 STAmount{
+
5651 USD,
+
5652 UINT64_C(283'9745962155617),
+
5653 -13}}}}));
+
5654 else
+
5655 BEAST_EXPECT(expectOffers(
+
5656 env,
+
5657 ed,
+
5658 1,
+
5659 {{Amounts{
+
5660 STAmount{
+
5661 ETH,
+
5662 UINT64_C(378'6327949540813),
+
5663 -13},
+
5664 STAmount{
+
5665 USD,
+
5666 UINT64_C(283'974596215561),
+
5667 -12}}}}));
+
5668 }
+
5669 else
+
5670 {
+
5671 if (!features[fixAMMv1_1])
+
5672 BEAST_EXPECT(expectOffers(
+
5673 env,
+
5674 ed,
+
5675 1,
+
5676 {{Amounts{
+
5677 STAmount{
+
5678 ETH,
+
5679 UINT64_C(325'299461620749),
+
5680 -12},
+
5681 STAmount{
+
5682 USD,
+
5683 UINT64_C(243'9745962155617),
+
5684 -13}}}}));
+
5685 else
+
5686 BEAST_EXPECT(expectOffers(
+
5687 env,
+
5688 ed,
+
5689 1,
+
5690 {{Amounts{
+
5691 STAmount{
+
5692 ETH,
+
5693 UINT64_C(325'299461620748),
+
5694 -12},
+
5695 STAmount{
+
5696 USD,
+
5697 UINT64_C(243'974596215561),
+
5698 -12}}}}));
+
5699 }
+
5700 }
+
5701 else if (i == 2)
+
5702 {
+
5703 if (rates.first == 1.5)
+
5704 {
+
5705 BEAST_EXPECT(expectOffers(
+
5706 env,
+
5707 ed,
+
5708 1,
+
5709 {{Amounts{
+
5710 STAmount{
+
5711 ETH, UINT64_C(378'6327949540812), -13},
+
5712 STAmount{
+
5713 USD,
+
5714 UINT64_C(283'9745962155609),
+
5715 -13}}}}));
+
5716 }
+
5717 else
+
5718 {
+
5719 BEAST_EXPECT(expectOffers(
+
5720 env,
+
5721 ed,
+
5722 1,
+
5723 {{Amounts{
+
5724 STAmount{
+
5725 ETH, UINT64_C(325'2994616207479), -13},
+
5726 STAmount{
+
5727 USD,
+
5728 UINT64_C(243'9745962155609),
+
5729 -13}}}}));
+
5730 }
+
5731 }
+
5732 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
+
5733 q[i] = Quality(Amounts{
+
5734 ETH(2'000) - env.balance(carol, ETH),
+
5735 env.balance(bob, USD) - USD(2'000)});
+
5736 }
+
5737 // AMM is better quality
+
5738 BEAST_EXPECT(q[1] > q[0]);
+
5739 // AMM and CLOB produce better quality
+
5740 BEAST_EXPECT(q[2] > q[1]);
+
5741 }
+
5742
+
5743 // Same as the offer-crossing but reduced offer quality
+
5744 for (auto i = 0; i < 3; ++i)
+
5745 {
+
5746 Env env(*this, features);
+
5747 prep(env, rates.first, rates.second);
+
5748 std::optional<AMM> amm;
+
5749 if (i == 0 || i == 2)
+
5750 {
+
5751 env(offer(ed, ETH(400), USD(250)), txflags(tfPassive));
+
5752 env.close();
+
5753 }
+
5754 if (i > 0)
+
5755 amm.emplace(env, ed, USD(1'000), ETH(1'000));
+
5756 env(offer(alice, USD(250), ETH(400)));
+
5757 env.close();
+
5758 // AMM is selected in both cases
+
5759 if (i > 0)
+
5760 {
+
5761 BEAST_EXPECT(!amm->expectBalances(
+
5762 USD(1'000), ETH(1'000), amm->tokens()));
+
5763 }
+
5764 // Partially crosses, AMM is selected, CLOB fails
+
5765 // limitQuality
+
5766 if (i == 2)
+
5767 {
+
5768 if (rates.first == 1.5)
+
5769 {
+
5770 if (!features[fixAMMv1_1])
+
5771 {
+
5772 BEAST_EXPECT(expectOffers(
+
5773 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
+
5774 BEAST_EXPECT(expectOffers(
+
5775 env,
+
5776 alice,
+
5777 1,
+
5778 {{Amounts{
+
5779 STAmount{
+
5780 USD, UINT64_C(40'5694150420947), -13},
+
5781 STAmount{
+
5782 ETH, UINT64_C(64'91106406735152), -14},
+
5783 }}}));
+
5784 }
+
5785 else
+
5786 {
+
5787 // Ed offer is partially crossed.
+
5788 // The updated rounding makes limitQuality
+
5789 // work if both amendments are enabled
+
5790 BEAST_EXPECT(expectOffers(
+
5791 env,
+
5792 ed,
+
5793 1,
+
5794 {{Amounts{
+
5795 STAmount{
+
5796 ETH, UINT64_C(335'0889359326475), -13},
+
5797 STAmount{
+
5798 USD, UINT64_C(209'4305849579047), -13},
+
5799 }}}));
+
5800 BEAST_EXPECT(expectOffers(env, alice, 0));
+
5801 }
+
5802 }
+
5803 else
+
5804 {
+
5805 if (!features[fixAMMv1_1])
+
5806 {
+
5807 // Ed offer is partially crossed.
+
5808 BEAST_EXPECT(expectOffers(
+
5809 env,
+
5810 ed,
+
5811 1,
+
5812 {{Amounts{
+
5813 STAmount{
+
5814 ETH, UINT64_C(335'0889359326485), -13},
+
5815 STAmount{
+
5816 USD, UINT64_C(209'4305849579053), -13},
+
5817 }}}));
+
5818 BEAST_EXPECT(expectOffers(env, alice, 0));
+
5819 }
+
5820 else
+
5821 {
+
5822 // Ed offer is partially crossed.
+
5823 BEAST_EXPECT(expectOffers(
+
5824 env,
+
5825 ed,
+
5826 1,
+
5827 {{Amounts{
+
5828 STAmount{
+
5829 ETH, UINT64_C(335'0889359326475), -13},
+
5830 STAmount{
+
5831 USD, UINT64_C(209'4305849579047), -13},
+
5832 }}}));
+
5833 BEAST_EXPECT(expectOffers(env, alice, 0));
+
5834 }
+
5835 }
+
5836 }
+
5837 }
+
5838
+
5839 // Strand selection
+
5840
+
5841 // Two book steps strand quality is 1.
+
5842 // AMM strand's best quality is equal to AMM's spot price
+
5843 // quality, which is 1. Both strands (steps) are adjusted
+
5844 // for the transfer fee in qualityUpperBound. In case
+
5845 // of two strands, AMM offers have better quality and are
+
5846 // consumed first, remaining liquidity is generated by CLOB
+
5847 // offers. Liquidity from two strands is better in this case
+
5848 // than in case of one strand with two book steps. Liquidity
+
5849 // from one strand with AMM has better quality than either one
+
5850 // strand with two book steps or two strands. It may appear
+
5851 // unintuitive, but one strand with AMM is optimized and
+
5852 // generates one AMM offer, while in case of two strands,
+
5853 // multiple AMM offers are generated, which results in slightly
+
5854 // worse overall quality.
+
5855 {
+
5856 std::array<Quality, 3> q;
+
5857 for (auto i = 0; i < 3; ++i)
+
5858 {
+
5859 Env env(*this, features);
+
5860 prep(env, rates.first, rates.second);
+
5861 std::optional<AMM> amm;
+
5862
+
5863 if (i == 0 || i == 2)
+
5864 {
+
5865 env(offer(ed, ETH(400), CAN(400)), txflags(tfPassive));
+
5866 env(offer(ed, CAN(400), USD(400))), txflags(tfPassive);
+
5867 env.close();
+
5868 }
+
5869
+
5870 if (i > 0)
+
5871 amm.emplace(env, ed, ETH(1'000), USD(1'000));
+
5872
+
5873 env(pay(carol, bob, USD(100)),
+
5874 path(~USD),
+
5875 path(~CAN, ~USD),
+
5876 sendmax(ETH(600)));
+
5877 env.close();
+
5878
+
5879 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
+
5880
+
5881 if (i == 2 && !features[fixAMMv1_1])
+
5882 {
+
5883 if (rates.first == 1.5)
+
5884 {
+
5885 // Liquidity is consumed from AMM strand only
+
5886 BEAST_EXPECT(amm->expectBalances(
+
5887 STAmount{ETH, UINT64_C(1'176'66038955758), -11},
+
5888 USD(850),
+
5889 amm->tokens()));
+
5890 }
+
5891 else
+
5892 {
+
5893 BEAST_EXPECT(amm->expectBalances(
+
5894 STAmount{
+
5895 ETH, UINT64_C(1'179'540094339627), -12},
+
5896 STAmount{USD, UINT64_C(847'7880529867501), -13},
+
5897 amm->tokens()));
+
5898 BEAST_EXPECT(expectOffers(
+
5899 env,
+
5900 ed,
+
5901 2,
+
5902 {{Amounts{
+
5903 STAmount{
+
5904 ETH,
+
5905 UINT64_C(343'3179205198749),
+
5906 -13},
+
5907 STAmount{
+
5908 CAN,
+
5909 UINT64_C(343'3179205198749),
+
5910 -13},
+
5911 },
+
5912 Amounts{
+
5913 STAmount{
+
5914 CAN,
+
5915 UINT64_C(362'2119470132499),
+
5916 -13},
+
5917 STAmount{
+
5918 USD,
+
5919 UINT64_C(362'2119470132499),
+
5920 -13},
+
5921 }}}));
+
5922 }
+
5923 }
+
5924 else if (i == 2)
+
5925 {
+
5926 if (rates.first == 1.5)
+
5927 {
+
5928 // Liquidity is consumed from AMM strand only
+
5929 BEAST_EXPECT(amm->expectBalances(
+
5930 STAmount{
+
5931 ETH, UINT64_C(1'176'660389557593), -12},
+
5932 USD(850),
+
5933 amm->tokens()));
+
5934 }
+
5935 else
+
5936 {
+
5937 BEAST_EXPECT(amm->expectBalances(
+
5938 STAmount{ETH, UINT64_C(1'179'54009433964), -11},
+
5939 STAmount{USD, UINT64_C(847'7880529867501), -13},
+
5940 amm->tokens()));
+
5941 BEAST_EXPECT(expectOffers(
+
5942 env,
+
5943 ed,
+
5944 2,
+
5945 {{Amounts{
+
5946 STAmount{
+
5947 ETH,
+
5948 UINT64_C(343'3179205198749),
+
5949 -13},
+
5950 STAmount{
+
5951 CAN,
+
5952 UINT64_C(343'3179205198749),
+
5953 -13},
+
5954 },
+
5955 Amounts{
+
5956 STAmount{
+
5957 CAN,
+
5958 UINT64_C(362'2119470132499),
+
5959 -13},
+
5960 STAmount{
+
5961 USD,
+
5962 UINT64_C(362'2119470132499),
+
5963 -13},
+
5964 }}}));
+
5965 }
+
5966 }
+
5967 q[i] = Quality(Amounts{
+
5968 ETH(2'000) - env.balance(carol, ETH),
+
5969 env.balance(bob, USD) - USD(2'000)});
+
5970 }
+
5971 BEAST_EXPECT(q[1] > q[0]);
+
5972 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
+
5973 }
+
5974 }
+
5975 }
+
5976
+
5977 void
+
5978 testFixDefaultInnerObj()
+
5979 {
+
5980 testcase("Fix Default Inner Object");
+
5981 using namespace jtx;
+
5982 FeatureBitset const all{supported_amendments()};
+
5983
+
5984 auto test = [&](FeatureBitset features,
+
5985 TER const& err1,
+
5986 TER const& err2,
+
5987 TER const& err3,
+
5988 TER const& err4,
+
5989 std::uint16_t tfee,
+
5990 bool closeLedger,
+
5991 std::optional<std::uint16_t> extra = std::nullopt) {
+
5992 Env env(*this, features);
+
5993 fund(env, gw, {alice}, XRP(1'000), {USD(10)});
+
5994 AMM amm(
+
5995 env,
+
5996 gw,
+
5997 XRP(10),
+
5998 USD(10),
+
5999 {.tfee = tfee, .close = closeLedger});
+
6000 amm.deposit(alice, USD(10), XRP(10));
+
6001 amm.vote(VoteArg{.account = alice, .tfee = tfee, .err = ter(err1)});
+
6002 amm.withdraw(WithdrawArg{
+
6003 .account = gw, .asset1Out = USD(1), .err = ter(err2)});
+
6004 // with the amendment disabled and ledger not closed,
+
6005 // second vote succeeds if the first vote sets the trading fee
+
6006 // to non-zero; if the first vote sets the trading fee to >0 &&
+
6007 // <9 then the second withdraw succeeds if the second vote sets
+
6008 // the trading fee so that the discounted fee is non-zero
+
6009 amm.vote(VoteArg{.account = alice, .tfee = 20, .err = ter(err3)});
+
6010 amm.withdraw(WithdrawArg{
+
6011 .account = gw, .asset1Out = USD(2), .err = ter(err4)});
+
6012 };
+
6013
+
6014 // ledger is closed after each transaction, vote/withdraw don't fail
+
6015 // regardless whether the amendment is enabled or not
+
6016 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, true);
+
6017 test(
+
6018 all - fixInnerObjTemplate,
6019 tesSUCCESS,
6020 tesSUCCESS,
6021 tesSUCCESS,
-
6022 0,
-
6023 true);
-
6024 // ledger is not closed after each transaction
-
6025 // vote/withdraw don't fail if the amendment is enabled
-
6026 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, false);
-
6027 // vote/withdraw fail if the amendment is not enabled
-
6028 // second vote/withdraw still fail: second vote fails because
-
6029 // the initial trading fee is 0, consequently second withdraw fails
-
6030 // because the second vote fails
-
6031 test(
-
6032 all - fixInnerObjTemplate,
-
6033 tefEXCEPTION,
+
6022 tesSUCCESS,
+
6023 0,
+
6024 true);
+
6025 // ledger is not closed after each transaction
+
6026 // vote/withdraw don't fail if the amendment is enabled
+
6027 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, false);
+
6028 // vote/withdraw fail if the amendment is not enabled
+
6029 // second vote/withdraw still fail: second vote fails because
+
6030 // the initial trading fee is 0, consequently second withdraw fails
+
6031 // because the second vote fails
+
6032 test(
+
6033 all - fixInnerObjTemplate,
6034 tefEXCEPTION,
6035 tefEXCEPTION,
6036 tefEXCEPTION,
-
6037 0,
-
6038 false);
-
6039 // if non-zero trading/discounted fee then vote/withdraw
-
6040 // don't fail whether the ledger is closed or not and
-
6041 // the amendment is enabled or not
-
6042 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, true);
-
6043 test(
-
6044 all - fixInnerObjTemplate,
-
6045 tesSUCCESS,
+
6037 tefEXCEPTION,
+
6038 0,
+
6039 false);
+
6040 // if non-zero trading/discounted fee then vote/withdraw
+
6041 // don't fail whether the ledger is closed or not and
+
6042 // the amendment is enabled or not
+
6043 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, true);
+
6044 test(
+
6045 all - fixInnerObjTemplate,
6046 tesSUCCESS,
6047 tesSUCCESS,
6048 tesSUCCESS,
-
6049 10,
-
6050 true);
-
6051 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, false);
-
6052 test(
-
6053 all - fixInnerObjTemplate,
-
6054 tesSUCCESS,
+
6049 tesSUCCESS,
+
6050 10,
+
6051 true);
+
6052 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, false);
+
6053 test(
+
6054 all - fixInnerObjTemplate,
6055 tesSUCCESS,
6056 tesSUCCESS,
6057 tesSUCCESS,
-
6058 10,
-
6059 false);
-
6060 // non-zero trading fee but discounted fee is 0, vote doesn't fail
-
6061 // but withdraw fails
-
6062 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 9, false);
-
6063 // second vote sets the trading fee to non-zero, consequently
-
6064 // second withdraw doesn't fail even if the amendment is not
-
6065 // enabled and the ledger is not closed
-
6066 test(
-
6067 all - fixInnerObjTemplate,
-
6068 tesSUCCESS,
-
6069 tefEXCEPTION,
-
6070 tesSUCCESS,
+
6058 tesSUCCESS,
+
6059 10,
+
6060 false);
+
6061 // non-zero trading fee but discounted fee is 0, vote doesn't fail
+
6062 // but withdraw fails
+
6063 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 9, false);
+
6064 // second vote sets the trading fee to non-zero, consequently
+
6065 // second withdraw doesn't fail even if the amendment is not
+
6066 // enabled and the ledger is not closed
+
6067 test(
+
6068 all - fixInnerObjTemplate,
+
6069 tesSUCCESS,
+
6070 tefEXCEPTION,
6071 tesSUCCESS,
-
6072 9,
-
6073 false);
-
6074 }
-
6075
-
6076 void
-
6077 testFixChangeSpotPriceQuality(FeatureBitset features)
-
6078 {
-
6079 testcase("Fix changeSpotPriceQuality");
-
6080 using namespace jtx;
-
6081
-
6082 enum class Status {
-
6083 SucceedShouldSucceedResize, // Succeed in pre-fix because
-
6084 // error allowance, succeed post-fix
-
6085 // because of offer resizing
-
6086 FailShouldSucceed, // Fail in pre-fix due to rounding,
-
6087 // succeed after fix because of XRP
-
6088 // side is generated first
-
6089 SucceedShouldFail, // Succeed in pre-fix, fail after fix
-
6090 // due to small quality difference
-
6091 Fail, // Both fail because the quality can't be matched
-
6092 Succeed // Both succeed
-
6093 };
-
6094 using enum Status;
-
6095 auto const xrpIouAmounts10_100 =
-
6096 TAmounts{XRPAmount{10}, IOUAmount{100}};
-
6097 auto const iouXrpAmounts10_100 =
-
6098 TAmounts{IOUAmount{10}, XRPAmount{100}};
-
6099 // clang-format off
-
6100 std::vector<std::tuple<std::string, std::string, Quality, std::uint16_t, Status>> tests = {
-
6101 //Pool In , Pool Out, Quality , Fee, Status
-
6102 {"0.001519763260828713", "1558701", Quality{5414253689393440221}, 1000, FailShouldSucceed},
-
6103 {"0.01099814367603737", "1892611", Quality{5482264816516900274}, 1000, FailShouldSucceed},
-
6104 {"0.78", "796599", Quality{5630392334958379008}, 1000, FailShouldSucceed},
-
6105 {"105439.2955578965", "49398693", Quality{5910869983721805038}, 400, FailShouldSucceed},
-
6106 {"12408293.23445213", "4340810521", Quality{5911611095910090752}, 997, FailShouldSucceed},
-
6107 {"1892611", "0.01099814367603737", Quality{6703103457950430139}, 1000, FailShouldSucceed},
-
6108 {"423028.8508101858", "3392804520", Quality{5837920340654162816}, 600, FailShouldSucceed},
-
6109 {"44565388.41001027", "73890647", Quality{6058976634606450001}, 1000, FailShouldSucceed},
-
6110 {"66831.68494832662", "16", Quality{6346111134641742975}, 0, FailShouldSucceed},
-
6111 {"675.9287302203422", "1242632304", Quality{5625960929244093294}, 300, FailShouldSucceed},
-
6112 {"7047.112186735699", "1649845866", Quality{5696855348026306945}, 504, FailShouldSucceed},
-
6113 {"840236.4402981238", "47419053", Quality{5982561601648018688}, 499, FailShouldSucceed},
-
6114 {"992715.618909774", "189445631733", Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
-
6115 {"504636667521", "185545883.9506651", Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
-
6116 {"992706.7218636649", "189447316000", Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
-
6117 {"1.068737911388205", "127860278877", Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
-
6118 {"17932506.56880419", "189308.6043676173", Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
-
6119 {"1.066379294658174", "128042251493", Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
-
6120 {"350131413924", "1576879.110907892", Quality{6487411636539049449}, 650, Fail},
-
6121 {"422093460", "2.731797662057464", Quality{6702911108534394924}, 1000, Fail},
-
6122 {"76128132223", "367172.7148422662", Quality{6487263463413514240}, 548, Fail},
-
6123 {"132701839250", "280703770.7695443", Quality{6273750681188885075}, 562, Fail},
-
6124 {"994165.7604612011", "189551302411", Quality{5697835592690668727}, 815, Fail},
-
6125 {"45053.33303227917", "86612695359", Quality{5625695218943638190}, 500, Fail},
-
6126 {"199649.077043865", "14017933007", Quality{5766034667318524880}, 324, Fail},
-
6127 {"27751824831.70903", "78896950", Quality{6272538159621630432}, 500, Fail},
-
6128 {"225.3731275781907", "156431793648", Quality{5477818047604078924}, 989, Fail},
-
6129 {"199649.077043865", "14017933007", Quality{5766036094462806309}, 324, Fail},
-
6130 {"3.590272027140361", "20677643641", Quality{5406056147042156356}, 808, Fail},
-
6131 {"1.070884664490231", "127604712776", Quality{5268620608623825741}, 293, Fail},
-
6132 {"3272.448829820197", "6275124076", Quality{5625710328924117902}, 81, Fail},
-
6133 {"0.009059512633902926", "7994028", Quality{5477511954775533172}, 1000, Fail},
-
6134 {"1", "1.0", Quality{0}, 100, Fail},
-
6135 {"1.0", "1", Quality{0}, 100, Fail},
-
6136 {"10", "10.0", Quality{xrpIouAmounts10_100}, 100, Fail},
-
6137 {"10.0", "10", Quality{iouXrpAmounts10_100}, 100, Fail},
-
6138 {"69864389131", "287631.4543025075", Quality{6487623473313516078}, 451, Succeed},
-
6139 {"4328342973", "12453825.99247381", Quality{6272522264364865181}, 997, Succeed},
-
6140 {"32347017", "7003.93031579449", Quality{6347261126087916670}, 1000, Succeed},
-
6141 {"61697206161", "36631.4583206413", Quality{6558965195382476659}, 500, Succeed},
-
6142 {"1654524979", "7028.659825511603", Quality{6487551345110052981}, 504, Succeed},
-
6143 {"88621.22277293179", "5128418948", Quality{5766347291552869205}, 380, Succeed},
-
6144 {"1892611", "0.01099814367603737", Quality{6703102780512015436}, 1000, Succeed},
-
6145 {"4542.639373338766", "24554809", Quality{5838994982188783710}, 0, Succeed},
-
6146 {"5132932546", "88542.99750172683", Quality{6419203342950054537}, 380, Succeed},
-
6147 {"78929964.1549083", "1506494795", Quality{5986890029845558688}, 589, Succeed},
-
6148 {"10096561906", "44727.72453735605", Quality{6487455290284644551}, 250, Succeed},
-
6149 {"5092.219565514988", "8768257694", Quality{5626349534958379008}, 503, Succeed},
-
6150 {"1819778294", "8305.084302902864", Quality{6487429398998540860}, 415, Succeed},
-
6151 {"6970462.633911943", "57359281", Quality{6054087899185946624}, 850, Succeed},
-
6152 {"3983448845", "2347.543644281467", Quality{6558965195382476659}, 856, Succeed},
-
6153 // This is a tiny offer 12drops/19321952e-15 it succeeds pre-amendment because of the error allowance.
-
6154 // Post amendment it is resized to 11drops/17711789e-15 but the quality is still less than
-
6155 // the target quality and the offer fails.
-
6156 {"771493171", "1.243473020567508", Quality{6707566798038544272}, 100, SucceedShouldFail},
-
6157 };
-
6158 // clang-format on
-
6159
-
6160 boost::regex rx("^\\d+$");
-
6161 boost::smatch match;
-
6162 // tests that succeed should have the same amounts pre-fix and post-fix
-
6163 std::vector<std::pair<STAmount, STAmount>> successAmounts;
-
6164 Env env(*this, features);
-
6165 auto rules = env.current()->rules();
-
6166 CurrentTransactionRulesGuard rg(rules);
-
6167 for (auto const& t : tests)
-
6168 {
-
6169 auto getPool = [&](std::string const& v, bool isXRP) {
-
6170 if (isXRP)
-
6171 return amountFromString(xrpIssue(), v);
-
6172 return amountFromString(noIssue(), v);
-
6173 };
-
6174 auto const& quality = std::get<Quality>(t);
-
6175 auto const tfee = std::get<std::uint16_t>(t);
-
6176 auto const status = std::get<Status>(t);
-
6177 auto const poolInIsXRP =
-
6178 boost::regex_search(std::get<0>(t), match, rx);
-
6179 auto const poolOutIsXRP =
-
6180 boost::regex_search(std::get<1>(t), match, rx);
-
6181 assert(!(poolInIsXRP && poolOutIsXRP));
-
6182 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
-
6183 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
-
6184 try
-
6185 {
-
6186 auto const amounts = changeSpotPriceQuality(
-
6187 Amounts{poolIn, poolOut},
-
6188 quality,
-
6189 tfee,
-
6190 env.current()->rules(),
-
6191 env.journal);
-
6192 if (amounts)
-
6193 {
-
6194 if (status == SucceedShouldSucceedResize)
-
6195 {
-
6196 if (!features[fixAMMv1_1])
-
6197 BEAST_EXPECT(Quality{*amounts} < quality);
-
6198 else
-
6199 BEAST_EXPECT(Quality{*amounts} >= quality);
-
6200 }
-
6201 else if (status == Succeed)
-
6202 {
-
6203 if (!features[fixAMMv1_1])
-
6204 BEAST_EXPECT(
-
6205 Quality{*amounts} >= quality ||
-
6206 withinRelativeDistance(
-
6207 Quality{*amounts}, quality, Number{1, -7}));
-
6208 else
-
6209 BEAST_EXPECT(Quality{*amounts} >= quality);
-
6210 }
-
6211 else if (status == FailShouldSucceed)
-
6212 {
-
6213 BEAST_EXPECT(
-
6214 features[fixAMMv1_1] &&
-
6215 Quality{*amounts} >= quality);
-
6216 }
-
6217 else if (status == SucceedShouldFail)
-
6218 {
-
6219 BEAST_EXPECT(
-
6220 !features[fixAMMv1_1] &&
-
6221 Quality{*amounts} < quality &&
-
6222 withinRelativeDistance(
-
6223 Quality{*amounts}, quality, Number{1, -7}));
-
6224 }
-
6225 }
-
6226 else
-
6227 {
-
6228 // Fails pre- and post-amendment because the quality can't
-
6229 // be matched. Verify by generating a tiny offer, which
-
6230 // doesn't match the quality. Exclude zero quality since
-
6231 // no offer is generated in this case.
-
6232 if (status == Fail && quality != Quality{0})
-
6233 {
-
6234 auto tinyOffer = [&]() {
-
6235 if (isXRP(poolIn))
-
6236 {
-
6237 auto const takerPays = STAmount{xrpIssue(), 1};
-
6238 return Amounts{
-
6239 takerPays,
-
6240 swapAssetIn(
-
6241 Amounts{poolIn, poolOut},
-
6242 takerPays,
-
6243 tfee)};
-
6244 }
-
6245 else if (isXRP(poolOut))
-
6246 {
-
6247 auto const takerGets = STAmount{xrpIssue(), 1};
-
6248 return Amounts{
-
6249 swapAssetOut(
-
6250 Amounts{poolIn, poolOut},
-
6251 takerGets,
-
6252 tfee),
-
6253 takerGets};
-
6254 }
-
6255 auto const takerPays = toAmount<STAmount>(
-
6256 getIssue(poolIn), Number{1, -10} * poolIn);
-
6257 return Amounts{
-
6258 takerPays,
-
6259 swapAssetIn(
-
6260 Amounts{poolIn, poolOut}, takerPays, tfee)};
-
6261 }();
-
6262 BEAST_EXPECT(Quality(tinyOffer) < quality);
-
6263 }
-
6264 else if (status == FailShouldSucceed)
-
6265 {
-
6266 BEAST_EXPECT(!features[fixAMMv1_1]);
-
6267 }
-
6268 else if (status == SucceedShouldFail)
-
6269 {
-
6270 BEAST_EXPECT(features[fixAMMv1_1]);
-
6271 }
-
6272 }
-
6273 }
-
6274 catch (std::runtime_error const& e)
-
6275 {
-
6276 BEAST_EXPECT(
-
6277 !strcmp(e.what(), "changeSpotPriceQuality failed"));
-
6278 BEAST_EXPECT(
-
6279 !features[fixAMMv1_1] && status == FailShouldSucceed);
-
6280 }
-
6281 }
-
6282
-
6283 // Test negative discriminant
-
6284 {
-
6285 // b**2 - 4 * a * c -> 1 * 1 - 4 * 1 * 1 = -3
-
6286 auto const res =
-
6287 solveQuadraticEqSmallest(Number{1}, Number{1}, Number{1});
-
6288 BEAST_EXPECT(!res.has_value());
-
6289 }
-
6290 }
-
6291
-
6292 void
-
6293 testMalformed()
-
6294 {
-
6295 using namespace jtx;
-
6296
-
6297 testAMM([&](AMM& ammAlice, Env& env) {
-
6298 WithdrawArg args{
-
6299 .flags = tfSingleAsset,
-
6300 .err = ter(temMALFORMED),
-
6301 };
-
6302 ammAlice.withdraw(args);
-
6303 });
-
6304
-
6305 testAMM([&](AMM& ammAlice, Env& env) {
-
6306 WithdrawArg args{
-
6307 .flags = tfOneAssetLPToken,
-
6308 .err = ter(temMALFORMED),
-
6309 };
-
6310 ammAlice.withdraw(args);
-
6311 });
-
6312
-
6313 testAMM([&](AMM& ammAlice, Env& env) {
-
6314 WithdrawArg args{
-
6315 .flags = tfLimitLPToken,
-
6316 .err = ter(temMALFORMED),
-
6317 };
-
6318 ammAlice.withdraw(args);
-
6319 });
-
6320
-
6321 testAMM([&](AMM& ammAlice, Env& env) {
-
6322 WithdrawArg args{
-
6323 .asset1Out = XRP(100),
-
6324 .asset2Out = XRP(100),
-
6325 .err = ter(temBAD_AMM_TOKENS),
-
6326 };
-
6327 ammAlice.withdraw(args);
-
6328 });
-
6329
-
6330 testAMM([&](AMM& ammAlice, Env& env) {
-
6331 WithdrawArg args{
-
6332 .asset1Out = XRP(100),
-
6333 .asset2Out = BAD(100),
-
6334 .err = ter(temBAD_CURRENCY),
-
6335 };
-
6336 ammAlice.withdraw(args);
-
6337 });
-
6338
-
6339 testAMM([&](AMM& ammAlice, Env& env) {
-
6340 Json::Value jv;
-
6341 jv[jss::TransactionType] = jss::AMMWithdraw;
-
6342 jv[jss::Flags] = tfLimitLPToken;
-
6343 jv[jss::Account] = alice.human();
-
6344 ammAlice.setTokens(jv);
-
6345 XRP(100).value().setJson(jv[jss::Amount]);
-
6346 USD(100).value().setJson(jv[jss::EPrice]);
-
6347 env(jv, ter(temBAD_AMM_TOKENS));
-
6348 });
-
6349 }
-
6350
-
6351 void
-
6352 testFixOverflowOffer(FeatureBitset features)
-
6353 {
-
6354 using namespace jtx;
-
6355 using namespace std::chrono;
-
6356 FeatureBitset const all{features};
-
6357
-
6358 Account const gatehub{"gatehub"};
-
6359 Account const bitstamp{"bitstamp"};
-
6360 Account const trader{"trader"};
-
6361 auto const usdGH = gatehub["USD"];
-
6362 auto const btcGH = gatehub["BTC"];
-
6363 auto const usdBIT = bitstamp["USD"];
-
6364
-
6365 struct InputSet
-
6366 {
-
6367 char const* testCase;
-
6368 double const poolUsdBIT;
-
6369 double const poolUsdGH;
-
6370 sendmax const sendMaxUsdBIT;
-
6371 STAmount const sendUsdGH;
-
6372 STAmount const failUsdGH;
-
6373 STAmount const failUsdGHr;
-
6374 STAmount const failUsdBIT;
-
6375 STAmount const failUsdBITr;
-
6376 STAmount const goodUsdGH;
-
6377 STAmount const goodUsdGHr;
-
6378 STAmount const goodUsdBIT;
-
6379 STAmount const goodUsdBITr;
-
6380 IOUAmount const lpTokenBalance;
-
6381 double const offer1BtcGH = 0.1;
-
6382 double const offer2BtcGH = 0.1;
-
6383 double const offer2UsdGH = 1;
-
6384 double const rateBIT = 0.0;
-
6385 double const rateGH = 0.0;
-
6386 };
-
6387
-
6388 using uint64_t = std::uint64_t;
-
6389
-
6390 for (auto const& input : {
-
6391 InputSet{
-
6392 .testCase = "Test Fix Overflow Offer", //
-
6393 .poolUsdBIT = 3, //
-
6394 .poolUsdGH = 273, //
-
6395 .sendMaxUsdBIT{usdBIT(50)}, //
-
6396 .sendUsdGH{usdGH, uint64_t(272'455089820359), -12}, //
-
6397 .failUsdGH = STAmount{0}, //
-
6398 .failUsdGHr = STAmount{0}, //
-
6399 .failUsdBIT{usdBIT, uint64_t(46'47826086956522), -14}, //
-
6400 .failUsdBITr{usdBIT, uint64_t(46'47826086956521), -14}, //
-
6401 .goodUsdGH{usdGH, uint64_t(96'7543114220382), -13}, //
-
6402 .goodUsdGHr{usdGH, uint64_t(96'7543114222965), -13}, //
-
6403 .goodUsdBIT{usdBIT, uint64_t(8'464739069120721), -15}, //
-
6404 .goodUsdBITr{usdBIT, uint64_t(8'464739069098152), -15}, //
-
6405 .lpTokenBalance = {28'61817604250837, -14}, //
-
6406 .offer1BtcGH = 0.1, //
-
6407 .offer2BtcGH = 0.1, //
-
6408 .offer2UsdGH = 1, //
-
6409 .rateBIT = 1.15, //
-
6410 .rateGH = 1.2, //
-
6411 },
-
6412 InputSet{
-
6413 .testCase = "Overflow test {1, 100, 0.111}", //
-
6414 .poolUsdBIT = 1, //
-
6415 .poolUsdGH = 100, //
-
6416 .sendMaxUsdBIT{usdBIT(0.111)}, //
-
6417 .sendUsdGH{usdGH, 100}, //
-
6418 .failUsdGH = STAmount{0}, //
-
6419 .failUsdGHr = STAmount{0}, //
-
6420 .failUsdBIT{usdBIT, uint64_t(1'111), -3}, //
-
6421 .failUsdBITr{usdBIT, uint64_t(1'111), -3}, //
-
6422 .goodUsdGH{usdGH, uint64_t(90'04347888284115), -14}, //
-
6423 .goodUsdGHr{usdGH, uint64_t(90'04347888284201), -14}, //
-
6424 .goodUsdBIT{usdBIT, uint64_t(1'111), -3}, //
-
6425 .goodUsdBITr{usdBIT, uint64_t(1'111), -3}, //
-
6426 .lpTokenBalance{10, 0}, //
-
6427 .offer1BtcGH = 1e-5, //
-
6428 .offer2BtcGH = 1, //
-
6429 .offer2UsdGH = 1e-5, //
-
6430 .rateBIT = 0, //
-
6431 .rateGH = 0, //
-
6432 },
-
6433 InputSet{
-
6434 .testCase = "Overflow test {1, 100, 1.00}", //
-
6435 .poolUsdBIT = 1, //
-
6436 .poolUsdGH = 100, //
-
6437 .sendMaxUsdBIT{usdBIT(1.00)}, //
-
6438 .sendUsdGH{usdGH, 100}, //
-
6439 .failUsdGH = STAmount{0}, //
-
6440 .failUsdGHr = STAmount{0}, //
-
6441 .failUsdBIT{usdBIT, uint64_t(2), 0}, //
-
6442 .failUsdBITr{usdBIT, uint64_t(2), 0}, //
-
6443 .goodUsdGH{usdGH, uint64_t(52'94379354424079), -14}, //
-
6444 .goodUsdGHr{usdGH, uint64_t(52'94379354424135), -14}, //
-
6445 .goodUsdBIT{usdBIT, uint64_t(2), 0}, //
-
6446 .goodUsdBITr{usdBIT, uint64_t(2), 0}, //
-
6447 .lpTokenBalance{10, 0}, //
-
6448 .offer1BtcGH = 1e-5, //
-
6449 .offer2BtcGH = 1, //
-
6450 .offer2UsdGH = 1e-5, //
-
6451 .rateBIT = 0, //
-
6452 .rateGH = 0, //
-
6453 },
-
6454 InputSet{
-
6455 .testCase = "Overflow test {1, 100, 4.6432}", //
-
6456 .poolUsdBIT = 1, //
-
6457 .poolUsdGH = 100, //
-
6458 .sendMaxUsdBIT{usdBIT(4.6432)}, //
-
6459 .sendUsdGH{usdGH, 100}, //
-
6460 .failUsdGH = STAmount{0}, //
-
6461 .failUsdGHr = STAmount{0}, //
-
6462 .failUsdBIT{usdBIT, uint64_t(5'6432), -4}, //
-
6463 .failUsdBITr{usdBIT, uint64_t(5'6432), -4}, //
-
6464 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
-
6465 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
-
6466 .goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
-
6467 .goodUsdBITr{usdBIT, uint64_t(2'821579689703954), -15}, //
-
6468 .lpTokenBalance{10, 0}, //
-
6469 .offer1BtcGH = 1e-5, //
-
6470 .offer2BtcGH = 1, //
-
6471 .offer2UsdGH = 1e-5, //
-
6472 .rateBIT = 0, //
-
6473 .rateGH = 0, //
-
6474 },
-
6475 InputSet{
-
6476 .testCase = "Overflow test {1, 100, 10}", //
-
6477 .poolUsdBIT = 1, //
-
6478 .poolUsdGH = 100, //
-
6479 .sendMaxUsdBIT{usdBIT(10)}, //
-
6480 .sendUsdGH{usdGH, 100}, //
-
6481 .failUsdGH = STAmount{0}, //
-
6482 .failUsdGHr = STAmount{0}, //
-
6483 .failUsdBIT{usdBIT, uint64_t(11), 0}, //
-
6484 .failUsdBITr{usdBIT, uint64_t(11), 0}, //
-
6485 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
-
6486 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
-
6487 .goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
-
6488 .goodUsdBITr{usdBIT, uint64_t(2'821579689703954), -15}, //
-
6489 .lpTokenBalance{10, 0}, //
-
6490 .offer1BtcGH = 1e-5, //
-
6491 .offer2BtcGH = 1, //
-
6492 .offer2UsdGH = 1e-5, //
-
6493 .rateBIT = 0, //
-
6494 .rateGH = 0, //
-
6495 },
-
6496 InputSet{
-
6497 .testCase = "Overflow test {50, 100, 5.55}", //
-
6498 .poolUsdBIT = 50, //
-
6499 .poolUsdGH = 100, //
-
6500 .sendMaxUsdBIT{usdBIT(5.55)}, //
-
6501 .sendUsdGH{usdGH, 100}, //
-
6502 .failUsdGH = STAmount{0}, //
-
6503 .failUsdGHr = STAmount{0}, //
-
6504 .failUsdBIT{usdBIT, uint64_t(55'55), -2}, //
-
6505 .failUsdBITr{usdBIT, uint64_t(55'55), -2}, //
-
6506 .goodUsdGH{usdGH, uint64_t(90'04347888284113), -14}, //
-
6507 .goodUsdGHr{usdGH, uint64_t(90'0434788828413), -13}, //
-
6508 .goodUsdBIT{usdBIT, uint64_t(55'55), -2}, //
-
6509 .goodUsdBITr{usdBIT, uint64_t(55'55), -2}, //
-
6510 .lpTokenBalance{uint64_t(70'71067811865475), -14}, //
-
6511 .offer1BtcGH = 1e-5, //
-
6512 .offer2BtcGH = 1, //
-
6513 .offer2UsdGH = 1e-5, //
-
6514 .rateBIT = 0, //
-
6515 .rateGH = 0, //
-
6516 },
-
6517 InputSet{
-
6518 .testCase = "Overflow test {50, 100, 50.00}", //
-
6519 .poolUsdBIT = 50, //
-
6520 .poolUsdGH = 100, //
-
6521 .sendMaxUsdBIT{usdBIT(50.00)}, //
-
6522 .sendUsdGH{usdGH, 100}, //
-
6523 .failUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
-
6524 .failUsdGHr{usdGH, uint64_t(52'94379354424092), -14}, //
-
6525 .failUsdBIT{usdBIT, uint64_t(100), 0}, //
-
6526 .failUsdBITr{usdBIT, uint64_t(100), 0}, //
-
6527 .goodUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
-
6528 .goodUsdGHr{usdGH, uint64_t(52'94379354424092), -14}, //
-
6529 .goodUsdBIT{usdBIT, uint64_t(100), 0}, //
-
6530 .goodUsdBITr{usdBIT, uint64_t(100), 0}, //
-
6531 .lpTokenBalance{uint64_t(70'71067811865475), -14}, //
-
6532 .offer1BtcGH = 1e-5, //
-
6533 .offer2BtcGH = 1, //
-
6534 .offer2UsdGH = 1e-5, //
-
6535 .rateBIT = 0, //
-
6536 .rateGH = 0, //
-
6537 },
-
6538 InputSet{
-
6539 .testCase = "Overflow test {50, 100, 232.16}", //
-
6540 .poolUsdBIT = 50, //
-
6541 .poolUsdGH = 100, //
-
6542 .sendMaxUsdBIT{usdBIT(232.16)}, //
-
6543 .sendUsdGH{usdGH, 100}, //
-
6544 .failUsdGH = STAmount{0}, //
-
6545 .failUsdGHr = STAmount{0}, //
-
6546 .failUsdBIT{usdBIT, uint64_t(282'16), -2}, //
-
6547 .failUsdBITr{usdBIT, uint64_t(282'16), -2}, //
-
6548 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
-
6549 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
-
6550 .goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
-
6551 .goodUsdBITr{usdBIT, uint64_t(141'0789844851962), -13}, //
-
6552 .lpTokenBalance{70'71067811865475, -14}, //
-
6553 .offer1BtcGH = 1e-5, //
-
6554 .offer2BtcGH = 1, //
-
6555 .offer2UsdGH = 1e-5, //
-
6556 .rateBIT = 0, //
-
6557 .rateGH = 0, //
-
6558 },
-
6559 InputSet{
-
6560 .testCase = "Overflow test {50, 100, 500}", //
-
6561 .poolUsdBIT = 50, //
-
6562 .poolUsdGH = 100, //
-
6563 .sendMaxUsdBIT{usdBIT(500)}, //
-
6564 .sendUsdGH{usdGH, 100}, //
-
6565 .failUsdGH = STAmount{0}, //
-
6566 .failUsdGHr = STAmount{0}, //
-
6567 .failUsdBIT{usdBIT, uint64_t(550), 0}, //
-
6568 .failUsdBITr{usdBIT, uint64_t(550), 0}, //
-
6569 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
-
6570 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
-
6571 .goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
-
6572 .goodUsdBITr{usdBIT, uint64_t(141'0789844851962), -13}, //
-
6573 .lpTokenBalance{70'71067811865475, -14}, //
-
6574 .offer1BtcGH = 1e-5, //
-
6575 .offer2BtcGH = 1, //
-
6576 .offer2UsdGH = 1e-5, //
-
6577 .rateBIT = 0, //
-
6578 .rateGH = 0, //
-
6579 },
-
6580 })
-
6581 {
-
6582 testcase(input.testCase);
-
6583 for (auto const& features :
-
6584 {all - fixAMMOverflowOffer, all | fixAMMOverflowOffer})
-
6585 {
-
6586 Env env(*this, features);
-
6587
-
6588 env.fund(XRP(5'000), gatehub, bitstamp, trader);
-
6589 env.close();
-
6590
-
6591 if (input.rateGH != 0.0)
-
6592 env(rate(gatehub, input.rateGH));
-
6593 if (input.rateBIT != 0.0)
-
6594 env(rate(bitstamp, input.rateBIT));
+
6072 tesSUCCESS,
+
6073 9,
+
6074 false);
+
6075 }
+
6076
+
6077 void
+
6078 testFixChangeSpotPriceQuality(FeatureBitset features)
+
6079 {
+
6080 testcase("Fix changeSpotPriceQuality");
+
6081 using namespace jtx;
+
6082
+
6083 std::string logs;
+
6084
+
6085 enum class Status {
+
6086 SucceedShouldSucceedResize, // Succeed in pre-fix because
+
6087 // error allowance, succeed post-fix
+
6088 // because of offer resizing
+
6089 FailShouldSucceed, // Fail in pre-fix due to rounding,
+
6090 // succeed after fix because of XRP
+
6091 // side is generated first
+
6092 SucceedShouldFail, // Succeed in pre-fix, fail after fix
+
6093 // due to small quality difference
+
6094 Fail, // Both fail because the quality can't be matched
+
6095 Succeed // Both succeed
+
6096 };
+
6097 using enum Status;
+
6098 auto const xrpIouAmounts10_100 =
+
6099 TAmounts{XRPAmount{10}, IOUAmount{100}};
+
6100 auto const iouXrpAmounts10_100 =
+
6101 TAmounts{IOUAmount{10}, XRPAmount{100}};
+
6102 // clang-format off
+
6103 std::vector<std::tuple<std::string, std::string, Quality, std::uint16_t, Status>> tests = {
+
6104 //Pool In , Pool Out, Quality , Fee, Status
+
6105 {"0.001519763260828713", "1558701", Quality{5414253689393440221}, 1000, FailShouldSucceed},
+
6106 {"0.01099814367603737", "1892611", Quality{5482264816516900274}, 1000, FailShouldSucceed},
+
6107 {"0.78", "796599", Quality{5630392334958379008}, 1000, FailShouldSucceed},
+
6108 {"105439.2955578965", "49398693", Quality{5910869983721805038}, 400, FailShouldSucceed},
+
6109 {"12408293.23445213", "4340810521", Quality{5911611095910090752}, 997, FailShouldSucceed},
+
6110 {"1892611", "0.01099814367603737", Quality{6703103457950430139}, 1000, FailShouldSucceed},
+
6111 {"423028.8508101858", "3392804520", Quality{5837920340654162816}, 600, FailShouldSucceed},
+
6112 {"44565388.41001027", "73890647", Quality{6058976634606450001}, 1000, FailShouldSucceed},
+
6113 {"66831.68494832662", "16", Quality{6346111134641742975}, 0, FailShouldSucceed},
+
6114 {"675.9287302203422", "1242632304", Quality{5625960929244093294}, 300, FailShouldSucceed},
+
6115 {"7047.112186735699", "1649845866", Quality{5696855348026306945}, 504, FailShouldSucceed},
+
6116 {"840236.4402981238", "47419053", Quality{5982561601648018688}, 499, FailShouldSucceed},
+
6117 {"992715.618909774", "189445631733", Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
+
6118 {"504636667521", "185545883.9506651", Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
+
6119 {"992706.7218636649", "189447316000", Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
+
6120 {"1.068737911388205", "127860278877", Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
+
6121 {"17932506.56880419", "189308.6043676173", Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
+
6122 {"1.066379294658174", "128042251493", Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
+
6123 {"350131413924", "1576879.110907892", Quality{6487411636539049449}, 650, Fail},
+
6124 {"422093460", "2.731797662057464", Quality{6702911108534394924}, 1000, Fail},
+
6125 {"76128132223", "367172.7148422662", Quality{6487263463413514240}, 548, Fail},
+
6126 {"132701839250", "280703770.7695443", Quality{6273750681188885075}, 562, Fail},
+
6127 {"994165.7604612011", "189551302411", Quality{5697835592690668727}, 815, Fail},
+
6128 {"45053.33303227917", "86612695359", Quality{5625695218943638190}, 500, Fail},
+
6129 {"199649.077043865", "14017933007", Quality{5766034667318524880}, 324, Fail},
+
6130 {"27751824831.70903", "78896950", Quality{6272538159621630432}, 500, Fail},
+
6131 {"225.3731275781907", "156431793648", Quality{5477818047604078924}, 989, Fail},
+
6132 {"199649.077043865", "14017933007", Quality{5766036094462806309}, 324, Fail},
+
6133 {"3.590272027140361", "20677643641", Quality{5406056147042156356}, 808, Fail},
+
6134 {"1.070884664490231", "127604712776", Quality{5268620608623825741}, 293, Fail},
+
6135 {"3272.448829820197", "6275124076", Quality{5625710328924117902}, 81, Fail},
+
6136 {"0.009059512633902926", "7994028", Quality{5477511954775533172}, 1000, Fail},
+
6137 {"1", "1.0", Quality{0}, 100, Fail},
+
6138 {"1.0", "1", Quality{0}, 100, Fail},
+
6139 {"10", "10.0", Quality{xrpIouAmounts10_100}, 100, Fail},
+
6140 {"10.0", "10", Quality{iouXrpAmounts10_100}, 100, Fail},
+
6141 {"69864389131", "287631.4543025075", Quality{6487623473313516078}, 451, Succeed},
+
6142 {"4328342973", "12453825.99247381", Quality{6272522264364865181}, 997, Succeed},
+
6143 {"32347017", "7003.93031579449", Quality{6347261126087916670}, 1000, Succeed},
+
6144 {"61697206161", "36631.4583206413", Quality{6558965195382476659}, 500, Succeed},
+
6145 {"1654524979", "7028.659825511603", Quality{6487551345110052981}, 504, Succeed},
+
6146 {"88621.22277293179", "5128418948", Quality{5766347291552869205}, 380, Succeed},
+
6147 {"1892611", "0.01099814367603737", Quality{6703102780512015436}, 1000, Succeed},
+
6148 {"4542.639373338766", "24554809", Quality{5838994982188783710}, 0, Succeed},
+
6149 {"5132932546", "88542.99750172683", Quality{6419203342950054537}, 380, Succeed},
+
6150 {"78929964.1549083", "1506494795", Quality{5986890029845558688}, 589, Succeed},
+
6151 {"10096561906", "44727.72453735605", Quality{6487455290284644551}, 250, Succeed},
+
6152 {"5092.219565514988", "8768257694", Quality{5626349534958379008}, 503, Succeed},
+
6153 {"1819778294", "8305.084302902864", Quality{6487429398998540860}, 415, Succeed},
+
6154 {"6970462.633911943", "57359281", Quality{6054087899185946624}, 850, Succeed},
+
6155 {"3983448845", "2347.543644281467", Quality{6558965195382476659}, 856, Succeed},
+
6156 // This is a tiny offer 12drops/19321952e-15 it succeeds pre-amendment because of the error allowance.
+
6157 // Post amendment it is resized to 11drops/17711789e-15 but the quality is still less than
+
6158 // the target quality and the offer fails.
+
6159 {"771493171", "1.243473020567508", Quality{6707566798038544272}, 100, SucceedShouldFail},
+
6160 };
+
6161 // clang-format on
+
6162
+
6163 boost::regex rx("^\\d+$");
+
6164 boost::smatch match;
+
6165 // tests that succeed should have the same amounts pre-fix and post-fix
+
6166 std::vector<std::pair<STAmount, STAmount>> successAmounts;
+
6167 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
+
6168 auto rules = env.current()->rules();
+
6169 CurrentTransactionRulesGuard rg(rules);
+
6170 for (auto const& t : tests)
+
6171 {
+
6172 auto getPool = [&](std::string const& v, bool isXRP) {
+
6173 if (isXRP)
+
6174 return amountFromString(xrpIssue(), v);
+
6175 return amountFromString(noIssue(), v);
+
6176 };
+
6177 auto const& quality = std::get<Quality>(t);
+
6178 auto const tfee = std::get<std::uint16_t>(t);
+
6179 auto const status = std::get<Status>(t);
+
6180 auto const poolInIsXRP =
+
6181 boost::regex_search(std::get<0>(t), match, rx);
+
6182 auto const poolOutIsXRP =
+
6183 boost::regex_search(std::get<1>(t), match, rx);
+
6184 assert(!(poolInIsXRP && poolOutIsXRP));
+
6185 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
+
6186 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
+
6187 try
+
6188 {
+
6189 auto const amounts = changeSpotPriceQuality(
+
6190 Amounts{poolIn, poolOut},
+
6191 quality,
+
6192 tfee,
+
6193 env.current()->rules(),
+
6194 env.journal);
+
6195 if (amounts)
+
6196 {
+
6197 if (status == SucceedShouldSucceedResize)
+
6198 {
+
6199 if (!features[fixAMMv1_1])
+
6200 BEAST_EXPECT(Quality{*amounts} < quality);
+
6201 else
+
6202 BEAST_EXPECT(Quality{*amounts} >= quality);
+
6203 }
+
6204 else if (status == Succeed)
+
6205 {
+
6206 if (!features[fixAMMv1_1])
+
6207 BEAST_EXPECT(
+
6208 Quality{*amounts} >= quality ||
+
6209 withinRelativeDistance(
+
6210 Quality{*amounts}, quality, Number{1, -7}));
+
6211 else
+
6212 BEAST_EXPECT(Quality{*amounts} >= quality);
+
6213 }
+
6214 else if (status == FailShouldSucceed)
+
6215 {
+
6216 BEAST_EXPECT(
+
6217 features[fixAMMv1_1] &&
+
6218 Quality{*amounts} >= quality);
+
6219 }
+
6220 else if (status == SucceedShouldFail)
+
6221 {
+
6222 BEAST_EXPECT(
+
6223 !features[fixAMMv1_1] &&
+
6224 Quality{*amounts} < quality &&
+
6225 withinRelativeDistance(
+
6226 Quality{*amounts}, quality, Number{1, -7}));
+
6227 }
+
6228 }
+
6229 else
+
6230 {
+
6231 // Fails pre- and post-amendment because the quality can't
+
6232 // be matched. Verify by generating a tiny offer, which
+
6233 // doesn't match the quality. Exclude zero quality since
+
6234 // no offer is generated in this case.
+
6235 if (status == Fail && quality != Quality{0})
+
6236 {
+
6237 auto tinyOffer = [&]() {
+
6238 if (isXRP(poolIn))
+
6239 {
+
6240 auto const takerPays = STAmount{xrpIssue(), 1};
+
6241 return Amounts{
+
6242 takerPays,
+
6243 swapAssetIn(
+
6244 Amounts{poolIn, poolOut},
+
6245 takerPays,
+
6246 tfee)};
+
6247 }
+
6248 else if (isXRP(poolOut))
+
6249 {
+
6250 auto const takerGets = STAmount{xrpIssue(), 1};
+
6251 return Amounts{
+
6252 swapAssetOut(
+
6253 Amounts{poolIn, poolOut},
+
6254 takerGets,
+
6255 tfee),
+
6256 takerGets};
+
6257 }
+
6258 auto const takerPays = toAmount<STAmount>(
+
6259 getIssue(poolIn), Number{1, -10} * poolIn);
+
6260 return Amounts{
+
6261 takerPays,
+
6262 swapAssetIn(
+
6263 Amounts{poolIn, poolOut}, takerPays, tfee)};
+
6264 }();
+
6265 BEAST_EXPECT(Quality(tinyOffer) < quality);
+
6266 }
+
6267 else if (status == FailShouldSucceed)
+
6268 {
+
6269 BEAST_EXPECT(!features[fixAMMv1_1]);
+
6270 }
+
6271 else if (status == SucceedShouldFail)
+
6272 {
+
6273 BEAST_EXPECT(features[fixAMMv1_1]);
+
6274 }
+
6275 }
+
6276 }
+
6277 catch (std::runtime_error const& e)
+
6278 {
+
6279 BEAST_EXPECT(
+
6280 !strcmp(e.what(), "changeSpotPriceQuality failed"));
+
6281 BEAST_EXPECT(
+
6282 !features[fixAMMv1_1] && status == FailShouldSucceed);
+
6283 }
+
6284 }
+
6285
+
6286 // Test negative discriminant
+
6287 {
+
6288 // b**2 - 4 * a * c -> 1 * 1 - 4 * 1 * 1 = -3
+
6289 auto const res =
+
6290 solveQuadraticEqSmallest(Number{1}, Number{1}, Number{1});
+
6291 BEAST_EXPECT(!res.has_value());
+
6292 }
+
6293 }
+
6294
+
6295 void
+
6296 testMalformed()
+
6297 {
+
6298 using namespace jtx;
+
6299
+
6300 testAMM([&](AMM& ammAlice, Env& env) {
+
6301 WithdrawArg args{
+
6302 .flags = tfSingleAsset,
+
6303 .err = ter(temMALFORMED),
+
6304 };
+
6305 ammAlice.withdraw(args);
+
6306 });
+
6307
+
6308 testAMM([&](AMM& ammAlice, Env& env) {
+
6309 WithdrawArg args{
+
6310 .flags = tfOneAssetLPToken,
+
6311 .err = ter(temMALFORMED),
+
6312 };
+
6313 ammAlice.withdraw(args);
+
6314 });
+
6315
+
6316 testAMM([&](AMM& ammAlice, Env& env) {
+
6317 WithdrawArg args{
+
6318 .flags = tfLimitLPToken,
+
6319 .err = ter(temMALFORMED),
+
6320 };
+
6321 ammAlice.withdraw(args);
+
6322 });
+
6323
+
6324 testAMM([&](AMM& ammAlice, Env& env) {
+
6325 WithdrawArg args{
+
6326 .asset1Out = XRP(100),
+
6327 .asset2Out = XRP(100),
+
6328 .err = ter(temBAD_AMM_TOKENS),
+
6329 };
+
6330 ammAlice.withdraw(args);
+
6331 });
+
6332
+
6333 testAMM([&](AMM& ammAlice, Env& env) {
+
6334 WithdrawArg args{
+
6335 .asset1Out = XRP(100),
+
6336 .asset2Out = BAD(100),
+
6337 .err = ter(temBAD_CURRENCY),
+
6338 };
+
6339 ammAlice.withdraw(args);
+
6340 });
+
6341
+
6342 testAMM([&](AMM& ammAlice, Env& env) {
+
6343 Json::Value jv;
+
6344 jv[jss::TransactionType] = jss::AMMWithdraw;
+
6345 jv[jss::Flags] = tfLimitLPToken;
+
6346 jv[jss::Account] = alice.human();
+
6347 ammAlice.setTokens(jv);
+
6348 XRP(100).value().setJson(jv[jss::Amount]);
+
6349 USD(100).value().setJson(jv[jss::EPrice]);
+
6350 env(jv, ter(temBAD_AMM_TOKENS));
+
6351 });
+
6352 }
+
6353
+
6354 void
+
6355 testFixOverflowOffer(FeatureBitset features)
+
6356 {
+
6357 using namespace jtx;
+
6358 using namespace std::chrono;
+
6359 FeatureBitset const all{features};
+
6360
+
6361 std::string logs;
+
6362
+
6363 Account const gatehub{"gatehub"};
+
6364 Account const bitstamp{"bitstamp"};
+
6365 Account const trader{"trader"};
+
6366 auto const usdGH = gatehub["USD"];
+
6367 auto const btcGH = gatehub["BTC"];
+
6368 auto const usdBIT = bitstamp["USD"];
+
6369
+
6370 struct InputSet
+
6371 {
+
6372 char const* testCase;
+
6373 double const poolUsdBIT;
+
6374 double const poolUsdGH;
+
6375 sendmax const sendMaxUsdBIT;
+
6376 STAmount const sendUsdGH;
+
6377 STAmount const failUsdGH;
+
6378 STAmount const failUsdGHr;
+
6379 STAmount const failUsdBIT;
+
6380 STAmount const failUsdBITr;
+
6381 STAmount const goodUsdGH;
+
6382 STAmount const goodUsdGHr;
+
6383 STAmount const goodUsdBIT;
+
6384 STAmount const goodUsdBITr;
+
6385 IOUAmount const lpTokenBalance;
+
6386 double const offer1BtcGH = 0.1;
+
6387 double const offer2BtcGH = 0.1;
+
6388 double const offer2UsdGH = 1;
+
6389 double const rateBIT = 0.0;
+
6390 double const rateGH = 0.0;
+
6391 };
+
6392
+
6393 using uint64_t = std::uint64_t;
+
6394
+
6395 for (auto const& input : {
+
6396 InputSet{
+
6397 .testCase = "Test Fix Overflow Offer", //
+
6398 .poolUsdBIT = 3, //
+
6399 .poolUsdGH = 273, //
+
6400 .sendMaxUsdBIT{usdBIT(50)}, //
+
6401 .sendUsdGH{usdGH, uint64_t(272'455089820359), -12}, //
+
6402 .failUsdGH = STAmount{0}, //
+
6403 .failUsdGHr = STAmount{0}, //
+
6404 .failUsdBIT{usdBIT, uint64_t(46'47826086956522), -14}, //
+
6405 .failUsdBITr{usdBIT, uint64_t(46'47826086956521), -14}, //
+
6406 .goodUsdGH{usdGH, uint64_t(96'7543114220382), -13}, //
+
6407 .goodUsdGHr{usdGH, uint64_t(96'7543114222965), -13}, //
+
6408 .goodUsdBIT{usdBIT, uint64_t(8'464739069120721), -15}, //
+
6409 .goodUsdBITr{usdBIT, uint64_t(8'464739069098152), -15}, //
+
6410 .lpTokenBalance = {28'61817604250837, -14}, //
+
6411 .offer1BtcGH = 0.1, //
+
6412 .offer2BtcGH = 0.1, //
+
6413 .offer2UsdGH = 1, //
+
6414 .rateBIT = 1.15, //
+
6415 .rateGH = 1.2, //
+
6416 },
+
6417 InputSet{
+
6418 .testCase = "Overflow test {1, 100, 0.111}", //
+
6419 .poolUsdBIT = 1, //
+
6420 .poolUsdGH = 100, //
+
6421 .sendMaxUsdBIT{usdBIT(0.111)}, //
+
6422 .sendUsdGH{usdGH, 100}, //
+
6423 .failUsdGH = STAmount{0}, //
+
6424 .failUsdGHr = STAmount{0}, //
+
6425 .failUsdBIT{usdBIT, uint64_t(1'111), -3}, //
+
6426 .failUsdBITr{usdBIT, uint64_t(1'111), -3}, //
+
6427 .goodUsdGH{usdGH, uint64_t(90'04347888284115), -14}, //
+
6428 .goodUsdGHr{usdGH, uint64_t(90'04347888284201), -14}, //
+
6429 .goodUsdBIT{usdBIT, uint64_t(1'111), -3}, //
+
6430 .goodUsdBITr{usdBIT, uint64_t(1'111), -3}, //
+
6431 .lpTokenBalance{10, 0}, //
+
6432 .offer1BtcGH = 1e-5, //
+
6433 .offer2BtcGH = 1, //
+
6434 .offer2UsdGH = 1e-5, //
+
6435 .rateBIT = 0, //
+
6436 .rateGH = 0, //
+
6437 },
+
6438 InputSet{
+
6439 .testCase = "Overflow test {1, 100, 1.00}", //
+
6440 .poolUsdBIT = 1, //
+
6441 .poolUsdGH = 100, //
+
6442 .sendMaxUsdBIT{usdBIT(1.00)}, //
+
6443 .sendUsdGH{usdGH, 100}, //
+
6444 .failUsdGH = STAmount{0}, //
+
6445 .failUsdGHr = STAmount{0}, //
+
6446 .failUsdBIT{usdBIT, uint64_t(2), 0}, //
+
6447 .failUsdBITr{usdBIT, uint64_t(2), 0}, //
+
6448 .goodUsdGH{usdGH, uint64_t(52'94379354424079), -14}, //
+
6449 .goodUsdGHr{usdGH, uint64_t(52'94379354424135), -14}, //
+
6450 .goodUsdBIT{usdBIT, uint64_t(2), 0}, //
+
6451 .goodUsdBITr{usdBIT, uint64_t(2), 0}, //
+
6452 .lpTokenBalance{10, 0}, //
+
6453 .offer1BtcGH = 1e-5, //
+
6454 .offer2BtcGH = 1, //
+
6455 .offer2UsdGH = 1e-5, //
+
6456 .rateBIT = 0, //
+
6457 .rateGH = 0, //
+
6458 },
+
6459 InputSet{
+
6460 .testCase = "Overflow test {1, 100, 4.6432}", //
+
6461 .poolUsdBIT = 1, //
+
6462 .poolUsdGH = 100, //
+
6463 .sendMaxUsdBIT{usdBIT(4.6432)}, //
+
6464 .sendUsdGH{usdGH, 100}, //
+
6465 .failUsdGH = STAmount{0}, //
+
6466 .failUsdGHr = STAmount{0}, //
+
6467 .failUsdBIT{usdBIT, uint64_t(5'6432), -4}, //
+
6468 .failUsdBITr{usdBIT, uint64_t(5'6432), -4}, //
+
6469 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
+
6470 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
+
6471 .goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
+
6472 .goodUsdBITr{usdBIT, uint64_t(2'821579689703954), -15}, //
+
6473 .lpTokenBalance{10, 0}, //
+
6474 .offer1BtcGH = 1e-5, //
+
6475 .offer2BtcGH = 1, //
+
6476 .offer2UsdGH = 1e-5, //
+
6477 .rateBIT = 0, //
+
6478 .rateGH = 0, //
+
6479 },
+
6480 InputSet{
+
6481 .testCase = "Overflow test {1, 100, 10}", //
+
6482 .poolUsdBIT = 1, //
+
6483 .poolUsdGH = 100, //
+
6484 .sendMaxUsdBIT{usdBIT(10)}, //
+
6485 .sendUsdGH{usdGH, 100}, //
+
6486 .failUsdGH = STAmount{0}, //
+
6487 .failUsdGHr = STAmount{0}, //
+
6488 .failUsdBIT{usdBIT, uint64_t(11), 0}, //
+
6489 .failUsdBITr{usdBIT, uint64_t(11), 0}, //
+
6490 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
+
6491 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
+
6492 .goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
+
6493 .goodUsdBITr{usdBIT, uint64_t(2'821579689703954), -15}, //
+
6494 .lpTokenBalance{10, 0}, //
+
6495 .offer1BtcGH = 1e-5, //
+
6496 .offer2BtcGH = 1, //
+
6497 .offer2UsdGH = 1e-5, //
+
6498 .rateBIT = 0, //
+
6499 .rateGH = 0, //
+
6500 },
+
6501 InputSet{
+
6502 .testCase = "Overflow test {50, 100, 5.55}", //
+
6503 .poolUsdBIT = 50, //
+
6504 .poolUsdGH = 100, //
+
6505 .sendMaxUsdBIT{usdBIT(5.55)}, //
+
6506 .sendUsdGH{usdGH, 100}, //
+
6507 .failUsdGH = STAmount{0}, //
+
6508 .failUsdGHr = STAmount{0}, //
+
6509 .failUsdBIT{usdBIT, uint64_t(55'55), -2}, //
+
6510 .failUsdBITr{usdBIT, uint64_t(55'55), -2}, //
+
6511 .goodUsdGH{usdGH, uint64_t(90'04347888284113), -14}, //
+
6512 .goodUsdGHr{usdGH, uint64_t(90'0434788828413), -13}, //
+
6513 .goodUsdBIT{usdBIT, uint64_t(55'55), -2}, //
+
6514 .goodUsdBITr{usdBIT, uint64_t(55'55), -2}, //
+
6515 .lpTokenBalance{uint64_t(70'71067811865475), -14}, //
+
6516 .offer1BtcGH = 1e-5, //
+
6517 .offer2BtcGH = 1, //
+
6518 .offer2UsdGH = 1e-5, //
+
6519 .rateBIT = 0, //
+
6520 .rateGH = 0, //
+
6521 },
+
6522 InputSet{
+
6523 .testCase = "Overflow test {50, 100, 50.00}", //
+
6524 .poolUsdBIT = 50, //
+
6525 .poolUsdGH = 100, //
+
6526 .sendMaxUsdBIT{usdBIT(50.00)}, //
+
6527 .sendUsdGH{usdGH, 100}, //
+
6528 .failUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
+
6529 .failUsdGHr{usdGH, uint64_t(52'94379354424092), -14}, //
+
6530 .failUsdBIT{usdBIT, uint64_t(100), 0}, //
+
6531 .failUsdBITr{usdBIT, uint64_t(100), 0}, //
+
6532 .goodUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
+
6533 .goodUsdGHr{usdGH, uint64_t(52'94379354424092), -14}, //
+
6534 .goodUsdBIT{usdBIT, uint64_t(100), 0}, //
+
6535 .goodUsdBITr{usdBIT, uint64_t(100), 0}, //
+
6536 .lpTokenBalance{uint64_t(70'71067811865475), -14}, //
+
6537 .offer1BtcGH = 1e-5, //
+
6538 .offer2BtcGH = 1, //
+
6539 .offer2UsdGH = 1e-5, //
+
6540 .rateBIT = 0, //
+
6541 .rateGH = 0, //
+
6542 },
+
6543 InputSet{
+
6544 .testCase = "Overflow test {50, 100, 232.16}", //
+
6545 .poolUsdBIT = 50, //
+
6546 .poolUsdGH = 100, //
+
6547 .sendMaxUsdBIT{usdBIT(232.16)}, //
+
6548 .sendUsdGH{usdGH, 100}, //
+
6549 .failUsdGH = STAmount{0}, //
+
6550 .failUsdGHr = STAmount{0}, //
+
6551 .failUsdBIT{usdBIT, uint64_t(282'16), -2}, //
+
6552 .failUsdBITr{usdBIT, uint64_t(282'16), -2}, //
+
6553 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
+
6554 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
+
6555 .goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
+
6556 .goodUsdBITr{usdBIT, uint64_t(141'0789844851962), -13}, //
+
6557 .lpTokenBalance{70'71067811865475, -14}, //
+
6558 .offer1BtcGH = 1e-5, //
+
6559 .offer2BtcGH = 1, //
+
6560 .offer2UsdGH = 1e-5, //
+
6561 .rateBIT = 0, //
+
6562 .rateGH = 0, //
+
6563 },
+
6564 InputSet{
+
6565 .testCase = "Overflow test {50, 100, 500}", //
+
6566 .poolUsdBIT = 50, //
+
6567 .poolUsdGH = 100, //
+
6568 .sendMaxUsdBIT{usdBIT(500)}, //
+
6569 .sendUsdGH{usdGH, 100}, //
+
6570 .failUsdGH = STAmount{0}, //
+
6571 .failUsdGHr = STAmount{0}, //
+
6572 .failUsdBIT{usdBIT, uint64_t(550), 0}, //
+
6573 .failUsdBITr{usdBIT, uint64_t(550), 0}, //
+
6574 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
+
6575 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
+
6576 .goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
+
6577 .goodUsdBITr{usdBIT, uint64_t(141'0789844851962), -13}, //
+
6578 .lpTokenBalance{70'71067811865475, -14}, //
+
6579 .offer1BtcGH = 1e-5, //
+
6580 .offer2BtcGH = 1, //
+
6581 .offer2UsdGH = 1e-5, //
+
6582 .rateBIT = 0, //
+
6583 .rateGH = 0, //
+
6584 },
+
6585 })
+
6586 {
+
6587 testcase(input.testCase);
+
6588 for (auto const& features :
+
6589 {all - fixAMMOverflowOffer, all | fixAMMOverflowOffer})
+
6590 {
+
6591 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
+
6592
+
6593 env.fund(XRP(5'000), gatehub, bitstamp, trader);
+
6594 env.close();
6595
-
6596 env(trust(trader, usdGH(10'000'000)));
-
6597 env(trust(trader, usdBIT(10'000'000)));
-
6598 env(trust(trader, btcGH(10'000'000)));
-
6599 env.close();
+
6596 if (input.rateGH != 0.0)
+
6597 env(rate(gatehub, input.rateGH));
+
6598 if (input.rateBIT != 0.0)
+
6599 env(rate(bitstamp, input.rateBIT));
6600
-
6601 env(pay(gatehub, trader, usdGH(100'000)));
-
6602 env(pay(gatehub, trader, btcGH(100'000)));
-
6603 env(pay(bitstamp, trader, usdBIT(100'000)));
+
6601 env(trust(trader, usdGH(10'000'000)));
+
6602 env(trust(trader, usdBIT(10'000'000)));
+
6603 env(trust(trader, btcGH(10'000'000)));
6604 env.close();
6605
-
6606 AMM amm{
-
6607 env,
-
6608 trader,
-
6609 usdGH(input.poolUsdGH),
-
6610 usdBIT(input.poolUsdBIT)};
-
6611 env.close();
-
6612
-
6613 IOUAmount const preSwapLPTokenBalance =
-
6614 amm.getLPTokensBalance();
-
6615
-
6616 env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
-
6617 env(offer(
-
6618 trader,
-
6619 btcGH(input.offer2BtcGH),
-
6620 usdGH(input.offer2UsdGH)));
-
6621 env.close();
-
6622
-
6623 env(pay(trader, trader, input.sendUsdGH),
-
6624 path(~usdGH),
-
6625 path(~btcGH, ~usdGH),
-
6626 sendmax(input.sendMaxUsdBIT),
-
6627 txflags(tfPartialPayment));
-
6628 env.close();
-
6629
-
6630 auto const failUsdGH =
-
6631 features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
-
6632 auto const failUsdBIT =
-
6633 features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
-
6634 auto const goodUsdGH =
-
6635 features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
-
6636 auto const goodUsdBIT =
-
6637 features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
-
6638 if (!features[fixAMMOverflowOffer])
-
6639 {
-
6640 BEAST_EXPECT(amm.expectBalances(
-
6641 failUsdGH, failUsdBIT, input.lpTokenBalance));
-
6642 }
-
6643 else
+
6606 env(pay(gatehub, trader, usdGH(100'000)));
+
6607 env(pay(gatehub, trader, btcGH(100'000)));
+
6608 env(pay(bitstamp, trader, usdBIT(100'000)));
+
6609 env.close();
+
6610
+
6611 AMM amm{
+
6612 env,
+
6613 trader,
+
6614 usdGH(input.poolUsdGH),
+
6615 usdBIT(input.poolUsdBIT)};
+
6616 env.close();
+
6617
+
6618 IOUAmount const preSwapLPTokenBalance =
+
6619 amm.getLPTokensBalance();
+
6620
+
6621 env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
+
6622 env(offer(
+
6623 trader,
+
6624 btcGH(input.offer2BtcGH),
+
6625 usdGH(input.offer2UsdGH)));
+
6626 env.close();
+
6627
+
6628 env(pay(trader, trader, input.sendUsdGH),
+
6629 path(~usdGH),
+
6630 path(~btcGH, ~usdGH),
+
6631 sendmax(input.sendMaxUsdBIT),
+
6632 txflags(tfPartialPayment));
+
6633 env.close();
+
6634
+
6635 auto const failUsdGH =
+
6636 features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
+
6637 auto const failUsdBIT =
+
6638 features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
+
6639 auto const goodUsdGH =
+
6640 features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
+
6641 auto const goodUsdBIT =
+
6642 features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
+
6643 if (!features[fixAMMOverflowOffer])
6644 {
6645 BEAST_EXPECT(amm.expectBalances(
-
6646 goodUsdGH, goodUsdBIT, input.lpTokenBalance));
-
6647
-
6648 // Invariant: LPToken balance must not change in a
-
6649 // payment or a swap transaction
-
6650 BEAST_EXPECT(
-
6651 amm.getLPTokensBalance() == preSwapLPTokenBalance);
+
6646 failUsdGH, failUsdBIT, input.lpTokenBalance));
+
6647 }
+
6648 else
+
6649 {
+
6650 BEAST_EXPECT(amm.expectBalances(
+
6651 goodUsdGH, goodUsdBIT, input.lpTokenBalance));
6652
-
6653 // Invariant: The square root of (product of the pool
-
6654 // balances) must be at least the LPTokenBalance
-
6655 Number const sqrtPoolProduct =
-
6656 root2(goodUsdGH * goodUsdBIT);
+
6653 // Invariant: LPToken balance must not change in a
+
6654 // payment or a swap transaction
+
6655 BEAST_EXPECT(
+
6656 amm.getLPTokensBalance() == preSwapLPTokenBalance);
6657
-
6658 // Include a tiny tolerance for the test cases using
-
6659 // .goodUsdGH{usdGH, uint64_t(35'44113971506987),
-
6660 // -14}, .goodUsdBIT{usdBIT,
-
6661 // uint64_t(2'821579689703915), -15},
-
6662 // These two values multiply
-
6663 // to 99.99999999999994227040383754105 which gets
-
6664 // internally rounded to 100, due to representation
-
6665 // error.
-
6666 BEAST_EXPECT(
-
6667 (sqrtPoolProduct + Number{1, -14} >=
-
6668 input.lpTokenBalance));
-
6669 }
-
6670 }
-
6671 }
-
6672 }
-
6673
-
6674 void
-
6675 testSwapRounding()
-
6676 {
-
6677 testcase("swapRounding");
-
6678 using namespace jtx;
-
6679
-
6680 const STAmount xrpPool{XRP, UINT64_C(51600'000981)};
-
6681 const STAmount iouPool{USD, UINT64_C(803040'9987141784), -10};
-
6682
-
6683 const STAmount xrpBob{XRP, UINT64_C(1092'878933)};
-
6684 const STAmount iouBob{
-
6685 USD, UINT64_C(3'988035892323031), -28}; // 3.9...e-13
-
6686
-
6687 testAMM(
-
6688 [&](AMM& amm, Env& env) {
-
6689 // Check our AMM starting conditions.
-
6690 auto [xrpBegin, iouBegin, lptBegin] = amm.balances(XRP, USD);
+
6658 // Invariant: The square root of (product of the pool
+
6659 // balances) must be at least the LPTokenBalance
+
6660 Number const sqrtPoolProduct =
+
6661 root2(goodUsdGH * goodUsdBIT);
+
6662
+
6663 // Include a tiny tolerance for the test cases using
+
6664 // .goodUsdGH{usdGH, uint64_t(35'44113971506987),
+
6665 // -14}, .goodUsdBIT{usdBIT,
+
6666 // uint64_t(2'821579689703915), -15},
+
6667 // These two values multiply
+
6668 // to 99.99999999999994227040383754105 which gets
+
6669 // internally rounded to 100, due to representation
+
6670 // error.
+
6671 BEAST_EXPECT(
+
6672 (sqrtPoolProduct + Number{1, -14} >=
+
6673 input.lpTokenBalance));
+
6674 }
+
6675 }
+
6676 }
+
6677 }
+
6678
+
6679 void
+
6680 testSwapRounding()
+
6681 {
+
6682 testcase("swapRounding");
+
6683 using namespace jtx;
+
6684
+
6685 const STAmount xrpPool{XRP, UINT64_C(51600'000981)};
+
6686 const STAmount iouPool{USD, UINT64_C(803040'9987141784), -10};
+
6687
+
6688 const STAmount xrpBob{XRP, UINT64_C(1092'878933)};
+
6689 const STAmount iouBob{
+
6690 USD, UINT64_C(3'988035892323031), -28}; // 3.9...e-13
6691
-
6692 // Set Bob's starting conditions.
-
6693 env.fund(xrpBob, bob);
-
6694 env.trust(USD(1'000'000), bob);
-
6695 env(pay(gw, bob, iouBob));
-
6696 env.close();
-
6697
-
6698 env(offer(bob, XRP(6300), USD(100'000)));
-
6699 env.close();
-
6700
-
6701 // Assert that AMM is unchanged.
-
6702 BEAST_EXPECT(
-
6703 amm.expectBalances(xrpBegin, iouBegin, amm.tokens()));
-
6704 },
-
6705 {{xrpPool, iouPool}},
-
6706 889,
-
6707 std::nullopt,
-
6708 {jtx::supported_amendments() | fixAMMv1_1});
-
6709 }
-
6710
-
6711 void
-
6712 testFixAMMOfferBlockedByLOB(FeatureBitset features)
-
6713 {
-
6714 testcase("AMM Offer Blocked By LOB");
-
6715 using namespace jtx;
-
6716
-
6717 // Low quality LOB offer blocks AMM liquidity
-
6718
-
6719 // USD/XRP crosses AMM
-
6720 {
-
6721 Env env(*this, features);
-
6722
-
6723 fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)});
-
6724 // This offer blocks AMM offer in pre-amendment
-
6725 env(offer(alice, XRP(1), USD(0.01)));
-
6726 env.close();
+
6692 testAMM(
+
6693 [&](AMM& amm, Env& env) {
+
6694 // Check our AMM starting conditions.
+
6695 auto [xrpBegin, iouBegin, lptBegin] = amm.balances(XRP, USD);
+
6696
+
6697 // Set Bob's starting conditions.
+
6698 env.fund(xrpBob, bob);
+
6699 env.trust(USD(1'000'000), bob);
+
6700 env(pay(gw, bob, iouBob));
+
6701 env.close();
+
6702
+
6703 env(offer(bob, XRP(6300), USD(100'000)));
+
6704 env.close();
+
6705
+
6706 // Assert that AMM is unchanged.
+
6707 BEAST_EXPECT(
+
6708 amm.expectBalances(xrpBegin, iouBegin, amm.tokens()));
+
6709 },
+
6710 {{xrpPool, iouPool}},
+
6711 889,
+
6712 std::nullopt,
+
6713 {jtx::supported_amendments() | fixAMMv1_1});
+
6714 }
+
6715
+
6716 void
+
6717 testFixAMMOfferBlockedByLOB(FeatureBitset features)
+
6718 {
+
6719 testcase("AMM Offer Blocked By LOB");
+
6720 using namespace jtx;
+
6721
+
6722 // Low quality LOB offer blocks AMM liquidity
+
6723
+
6724 // USD/XRP crosses AMM
+
6725 {
+
6726 Env env(*this, features);
6727
-
6728 AMM amm(env, gw, XRP(200'000), USD(100'000));
-
6729
-
6730 // The offer doesn't cross AMM in pre-amendment code
-
6731 // It crosses AMM in post-amendment code
-
6732 env(offer(carol, USD(0.49), XRP(1)));
-
6733 env.close();
+
6728 fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)});
+
6729 // This offer blocks AMM offer in pre-amendment
+
6730 env(offer(alice, XRP(1), USD(0.01)));
+
6731 env.close();
+
6732
+
6733 AMM amm(env, gw, XRP(200'000), USD(100'000));
6734
-
6735 if (!features[fixAMMv1_1])
-
6736 {
-
6737 BEAST_EXPECT(amm.expectBalances(
-
6738 XRP(200'000), USD(100'000), amm.tokens()));
-
6739 BEAST_EXPECT(expectOffers(
-
6740 env, alice, 1, {{Amounts{XRP(1), USD(0.01)}}}));
-
6741 // Carol's offer is blocked by alice's offer
-
6742 BEAST_EXPECT(expectOffers(
-
6743 env, carol, 1, {{Amounts{USD(0.49), XRP(1)}}}));
-
6744 }
-
6745 else
-
6746 {
-
6747 BEAST_EXPECT(amm.expectBalances(
-
6748 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
-
6749 BEAST_EXPECT(expectOffers(
-
6750 env, alice, 1, {{Amounts{XRP(1), USD(0.01)}}}));
-
6751 // Carol's offer crosses AMM
-
6752 BEAST_EXPECT(expectOffers(env, carol, 0));
-
6753 }
-
6754 }
-
6755
-
6756 // There is no blocking offer, the same AMM liquidity is consumed
-
6757 // pre- and post-amendment.
-
6758 {
-
6759 Env env(*this, features);
+
6735 // The offer doesn't cross AMM in pre-amendment code
+
6736 // It crosses AMM in post-amendment code
+
6737 env(offer(carol, USD(0.49), XRP(1)));
+
6738 env.close();
+
6739
+
6740 if (!features[fixAMMv1_1])
+
6741 {
+
6742 BEAST_EXPECT(amm.expectBalances(
+
6743 XRP(200'000), USD(100'000), amm.tokens()));
+
6744 BEAST_EXPECT(expectOffers(
+
6745 env, alice, 1, {{Amounts{XRP(1), USD(0.01)}}}));
+
6746 // Carol's offer is blocked by alice's offer
+
6747 BEAST_EXPECT(expectOffers(
+
6748 env, carol, 1, {{Amounts{USD(0.49), XRP(1)}}}));
+
6749 }
+
6750 else
+
6751 {
+
6752 BEAST_EXPECT(amm.expectBalances(
+
6753 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
+
6754 BEAST_EXPECT(expectOffers(
+
6755 env, alice, 1, {{Amounts{XRP(1), USD(0.01)}}}));
+
6756 // Carol's offer crosses AMM
+
6757 BEAST_EXPECT(expectOffers(env, carol, 0));
+
6758 }
+
6759 }
6760
-
6761 fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)});
-
6762 // There is no blocking offer
-
6763 // env(offer(alice, XRP(1), USD(0.01)));
-
6764
-
6765 AMM amm(env, gw, XRP(200'000), USD(100'000));
-
6766
-
6767 // The offer crosses AMM
-
6768 env(offer(carol, USD(0.49), XRP(1)));
-
6769 env.close();
-
6770
-
6771 // The same result as with the blocking offer
-
6772 BEAST_EXPECT(amm.expectBalances(
-
6773 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
-
6774 // Carol's offer crosses AMM
-
6775 BEAST_EXPECT(expectOffers(env, carol, 0));
-
6776 }
-
6777
-
6778 // XRP/USD crosses AMM
-
6779 {
-
6780 Env env(*this, features);
-
6781 fund(env, gw, {alice, carol, bob}, XRP(10'000), {USD(1'000)});
+
6761 // There is no blocking offer, the same AMM liquidity is consumed
+
6762 // pre- and post-amendment.
+
6763 {
+
6764 Env env(*this, features);
+
6765
+
6766 fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)});
+
6767 // There is no blocking offer
+
6768 // env(offer(alice, XRP(1), USD(0.01)));
+
6769
+
6770 AMM amm(env, gw, XRP(200'000), USD(100'000));
+
6771
+
6772 // The offer crosses AMM
+
6773 env(offer(carol, USD(0.49), XRP(1)));
+
6774 env.close();
+
6775
+
6776 // The same result as with the blocking offer
+
6777 BEAST_EXPECT(amm.expectBalances(
+
6778 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
+
6779 // Carol's offer crosses AMM
+
6780 BEAST_EXPECT(expectOffers(env, carol, 0));
+
6781 }
6782
-
6783 // This offer blocks AMM offer in pre-amendment
-
6784 // It crosses AMM in post-amendment code
-
6785 env(offer(bob, USD(1), XRPAmount(500)));
-
6786 env.close();
-
6787 AMM amm(env, alice, XRP(1'000), USD(500));
-
6788 env(offer(carol, XRP(100), USD(55)));
-
6789 env.close();
-
6790 if (!features[fixAMMv1_1])
-
6791 {
-
6792 BEAST_EXPECT(
-
6793 amm.expectBalances(XRP(1'000), USD(500), amm.tokens()));
-
6794 BEAST_EXPECT(expectOffers(
-
6795 env, bob, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
-
6796 BEAST_EXPECT(expectOffers(
-
6797 env, carol, 1, {{Amounts{XRP(100), USD(55)}}}));
-
6798 }
-
6799 else
-
6800 {
-
6801 BEAST_EXPECT(amm.expectBalances(
-
6802 XRPAmount(909'090'909),
-
6803 STAmount{USD, UINT64_C(550'000000055), -9},
-
6804 amm.tokens()));
-
6805 BEAST_EXPECT(expectOffers(
-
6806 env,
-
6807 carol,
-
6808 1,
-
6809 {{Amounts{
-
6810 XRPAmount{9'090'909},
-
6811 STAmount{USD, 4'99999995, -8}}}}));
-
6812 BEAST_EXPECT(expectOffers(
-
6813 env, bob, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
-
6814 }
-
6815 }
-
6816
-
6817 // There is no blocking offer, the same AMM liquidity is consumed
-
6818 // pre- and post-amendment.
-
6819 {
-
6820 Env env(*this, features);
-
6821 fund(env, gw, {alice, carol, bob}, XRP(10'000), {USD(1'000)});
-
6822
-
6823 AMM amm(env, alice, XRP(1'000), USD(500));
-
6824 env(offer(carol, XRP(100), USD(55)));
-
6825 env.close();
-
6826 BEAST_EXPECT(amm.expectBalances(
-
6827 XRPAmount(909'090'909),
-
6828 STAmount{USD, UINT64_C(550'000000055), -9},
-
6829 amm.tokens()));
-
6830 BEAST_EXPECT(expectOffers(
-
6831 env,
-
6832 carol,
-
6833 1,
-
6834 {{Amounts{
-
6835 XRPAmount{9'090'909}, STAmount{USD, 4'99999995, -8}}}}));
-
6836 }
-
6837 }
-
6838
-
6839 void
-
6840 testLPTokenBalance(FeatureBitset features)
-
6841 {
-
6842 using namespace jtx;
+
6783 // XRP/USD crosses AMM
+
6784 {
+
6785 Env env(*this, features);
+
6786 fund(env, gw, {alice, carol, bob}, XRP(10'000), {USD(1'000)});
+
6787
+
6788 // This offer blocks AMM offer in pre-amendment
+
6789 // It crosses AMM in post-amendment code
+
6790 env(offer(bob, USD(1), XRPAmount(500)));
+
6791 env.close();
+
6792 AMM amm(env, alice, XRP(1'000), USD(500));
+
6793 env(offer(carol, XRP(100), USD(55)));
+
6794 env.close();
+
6795 if (!features[fixAMMv1_1])
+
6796 {
+
6797 BEAST_EXPECT(
+
6798 amm.expectBalances(XRP(1'000), USD(500), amm.tokens()));
+
6799 BEAST_EXPECT(expectOffers(
+
6800 env, bob, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
+
6801 BEAST_EXPECT(expectOffers(
+
6802 env, carol, 1, {{Amounts{XRP(100), USD(55)}}}));
+
6803 }
+
6804 else
+
6805 {
+
6806 BEAST_EXPECT(amm.expectBalances(
+
6807 XRPAmount(909'090'909),
+
6808 STAmount{USD, UINT64_C(550'000000055), -9},
+
6809 amm.tokens()));
+
6810 BEAST_EXPECT(expectOffers(
+
6811 env,
+
6812 carol,
+
6813 1,
+
6814 {{Amounts{
+
6815 XRPAmount{9'090'909},
+
6816 STAmount{USD, 4'99999995, -8}}}}));
+
6817 BEAST_EXPECT(expectOffers(
+
6818 env, bob, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
+
6819 }
+
6820 }
+
6821
+
6822 // There is no blocking offer, the same AMM liquidity is consumed
+
6823 // pre- and post-amendment.
+
6824 {
+
6825 Env env(*this, features);
+
6826 fund(env, gw, {alice, carol, bob}, XRP(10'000), {USD(1'000)});
+
6827
+
6828 AMM amm(env, alice, XRP(1'000), USD(500));
+
6829 env(offer(carol, XRP(100), USD(55)));
+
6830 env.close();
+
6831 BEAST_EXPECT(amm.expectBalances(
+
6832 XRPAmount(909'090'909),
+
6833 STAmount{USD, UINT64_C(550'000000055), -9},
+
6834 amm.tokens()));
+
6835 BEAST_EXPECT(expectOffers(
+
6836 env,
+
6837 carol,
+
6838 1,
+
6839 {{Amounts{
+
6840 XRPAmount{9'090'909}, STAmount{USD, 4'99999995, -8}}}}));
+
6841 }
+
6842 }
6843
-
6844 // Last Liquidity Provider is the issuer of one token
-
6845 {
-
6846 Env env(*this, features);
-
6847 fund(
-
6848 env,
-
6849 gw,
-
6850 {alice, carol},
-
6851 XRP(1'000'000'000),
-
6852 {USD(1'000'000'000)});
-
6853 AMM amm(env, gw, XRP(2), USD(1));
-
6854 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
-
6855 amm.deposit(carol, IOUAmount{1'000'000});
-
6856 amm.withdrawAll(alice);
-
6857 amm.withdrawAll(carol);
-
6858 auto const lpToken = getAccountLines(
-
6859 env, gw, amm.lptIssue())[jss::lines][0u][jss::balance];
-
6860 auto const lpTokenBalance =
-
6861 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
-
6862 BEAST_EXPECT(
-
6863 lpToken == "1414.213562373095" &&
-
6864 lpTokenBalance == "1414.213562373");
-
6865 if (!features[fixAMMv1_1])
-
6866 {
-
6867 amm.withdrawAll(gw, std::nullopt, ter(tecAMM_BALANCE));
-
6868 BEAST_EXPECT(amm.ammExists());
-
6869 }
-
6870 else
+
6844 void
+
6845 testLPTokenBalance(FeatureBitset features)
+
6846 {
+
6847 using namespace jtx;
+
6848
+
6849 // Last Liquidity Provider is the issuer of one token
+
6850 {
+
6851 Env env(*this, features);
+
6852 fund(
+
6853 env,
+
6854 gw,
+
6855 {alice, carol},
+
6856 XRP(1'000'000'000),
+
6857 {USD(1'000'000'000)});
+
6858 AMM amm(env, gw, XRP(2), USD(1));
+
6859 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
+
6860 amm.deposit(carol, IOUAmount{1'000'000});
+
6861 amm.withdrawAll(alice);
+
6862 amm.withdrawAll(carol);
+
6863 auto const lpToken = getAccountLines(
+
6864 env, gw, amm.lptIssue())[jss::lines][0u][jss::balance];
+
6865 auto const lpTokenBalance =
+
6866 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
+
6867 BEAST_EXPECT(
+
6868 lpToken == "1414.213562373095" &&
+
6869 lpTokenBalance == "1414.213562373");
+
6870 if (!features[fixAMMv1_1])
6871 {
-
6872 amm.withdrawAll(gw);
-
6873 BEAST_EXPECT(!amm.ammExists());
+
6872 amm.withdrawAll(gw, std::nullopt, ter(tecAMM_BALANCE));
+
6873 BEAST_EXPECT(amm.ammExists());
6874 }
-
6875 }
-
6876
-
6877 // Last Liquidity Provider is the issuer of two tokens, or not
-
6878 // the issuer
-
6879 for (auto const& lp : {gw, bob})
-
6880 {
-
6881 Env env(*this, features);
-
6882 auto const ABC = gw["ABC"];
-
6883 fund(
-
6884 env,
-
6885 gw,
-
6886 {alice, carol, bob},
-
6887 XRP(1'000),
-
6888 {USD(1'000'000'000), ABC(1'000'000'000'000)});
-
6889 AMM amm(env, lp, ABC(2'000'000), USD(1));
-
6890 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
-
6891 amm.deposit(carol, IOUAmount{1'000'000});
-
6892 amm.withdrawAll(alice);
-
6893 amm.withdrawAll(carol);
-
6894 auto const lpToken = getAccountLines(
-
6895 env, lp, amm.lptIssue())[jss::lines][0u][jss::balance];
-
6896 auto const lpTokenBalance =
-
6897 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
-
6898 BEAST_EXPECT(
-
6899 lpToken == "1414.213562373095" &&
-
6900 lpTokenBalance == "1414.213562373");
-
6901 if (!features[fixAMMv1_1])
-
6902 {
-
6903 amm.withdrawAll(lp, std::nullopt, ter(tecAMM_BALANCE));
-
6904 BEAST_EXPECT(amm.ammExists());
-
6905 }
-
6906 else
+
6875 else
+
6876 {
+
6877 amm.withdrawAll(gw);
+
6878 BEAST_EXPECT(!amm.ammExists());
+
6879 }
+
6880 }
+
6881
+
6882 // Last Liquidity Provider is the issuer of two tokens, or not
+
6883 // the issuer
+
6884 for (auto const& lp : {gw, bob})
+
6885 {
+
6886 Env env(*this, features);
+
6887 auto const ABC = gw["ABC"];
+
6888 fund(
+
6889 env,
+
6890 gw,
+
6891 {alice, carol, bob},
+
6892 XRP(1'000),
+
6893 {USD(1'000'000'000), ABC(1'000'000'000'000)});
+
6894 AMM amm(env, lp, ABC(2'000'000), USD(1));
+
6895 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
+
6896 amm.deposit(carol, IOUAmount{1'000'000});
+
6897 amm.withdrawAll(alice);
+
6898 amm.withdrawAll(carol);
+
6899 auto const lpToken = getAccountLines(
+
6900 env, lp, amm.lptIssue())[jss::lines][0u][jss::balance];
+
6901 auto const lpTokenBalance =
+
6902 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
+
6903 BEAST_EXPECT(
+
6904 lpToken == "1414.213562373095" &&
+
6905 lpTokenBalance == "1414.213562373");
+
6906 if (!features[fixAMMv1_1])
6907 {
-
6908 amm.withdrawAll(lp);
-
6909 BEAST_EXPECT(!amm.ammExists());
+
6908 amm.withdrawAll(lp, std::nullopt, ter(tecAMM_BALANCE));
+
6909 BEAST_EXPECT(amm.ammExists());
6910 }
-
6911 }
-
6912
-
6913 // More than one Liquidity Provider
-
6914 // XRP/IOU
-
6915 {
-
6916 Env env(*this, features);
-
6917 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)});
-
6918 AMM amm(env, gw, XRP(10), USD(10));
-
6919 amm.deposit(alice, 1'000);
-
6920 auto res =
-
6921 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
-
6922 BEAST_EXPECT(res && !res.value());
-
6923 res =
-
6924 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
-
6925 BEAST_EXPECT(res && !res.value());
-
6926 }
-
6927 // IOU/IOU, issuer of both IOU
-
6928 {
-
6929 Env env(*this, features);
-
6930 fund(env, gw, {alice}, XRP(1'000), {USD(1'000), EUR(1'000)});
-
6931 AMM amm(env, gw, EUR(10), USD(10));
-
6932 amm.deposit(alice, 1'000);
-
6933 auto res =
-
6934 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
-
6935 BEAST_EXPECT(res && !res.value());
-
6936 res =
-
6937 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
-
6938 BEAST_EXPECT(res && !res.value());
-
6939 }
-
6940 // IOU/IOU, issuer of one IOU
-
6941 {
-
6942 Env env(*this, features);
-
6943 Account const gw1("gw1");
-
6944 auto const YAN = gw1["YAN"];
-
6945 fund(env, gw, {gw1}, XRP(1'000), {USD(1'000)});
-
6946 fund(env, gw1, {gw}, XRP(1'000), {YAN(1'000)}, Fund::IOUOnly);
-
6947 AMM amm(env, gw1, YAN(10), USD(10));
-
6948 amm.deposit(gw, 1'000);
-
6949 auto res =
-
6950 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
-
6951 BEAST_EXPECT(res && !res.value());
-
6952 res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw1);
-
6953 BEAST_EXPECT(res && !res.value());
-
6954 }
-
6955 }
-
6956
-
6957 void
-
6958 testAMMClawback(FeatureBitset features)
-
6959 {
-
6960 testcase("test clawback from AMM account");
-
6961 using namespace jtx;
-
6962
-
6963 // Issuer has clawback enabled
-
6964 Env env(*this, features);
-
6965 env.fund(XRP(1'000), gw);
-
6966 env(fset(gw, asfAllowTrustLineClawback));
-
6967 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
-
6968 env.close();
-
6969
-
6970 // If featureAMMClawback is not enabled, AMMCreate is not allowed for
-
6971 // clawback-enabled issuer
-
6972 if (!features[featureAMMClawback])
-
6973 {
-
6974 AMM amm(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
-
6975 AMM amm1(env, alice, USD(100), XRP(100), ter(tecNO_PERMISSION));
-
6976 env(fclear(gw, asfAllowTrustLineClawback));
-
6977 env.close();
-
6978 // Can't be cleared
-
6979 AMM amm2(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
-
6980 }
-
6981 // If featureAMMClawback is enabled, AMMCreate is allowed for
-
6982 // clawback-enabled issuer. Clawback from the AMM Account is not
-
6983 // allowed, which will return tecAMM_ACCOUNT. We can only use
-
6984 // AMMClawback transaction to claw back from AMM Account.
-
6985 else
-
6986 {
-
6987 AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
-
6988 AMM amm1(env, alice, USD(100), XRP(200), ter(tecDUPLICATE));
-
6989
-
6990 // Construct the amount being clawed back using AMM account.
-
6991 // By doing this, we make the clawback transaction's Amount field's
-
6992 // subfield `issuer` to be the AMM account, which means
-
6993 // we are clawing back from an AMM account. This should return an
-
6994 // tecAMM_ACCOUNT error because regular Clawback transaction is not
-
6995 // allowed for clawing back from an AMM account. Please notice the
-
6996 // `issuer` subfield represents the account being clawed back, which
-
6997 // is confusing.
-
6998 Issue usd(USD.issue().currency, amm.ammAccount());
-
6999 auto amount = amountFromString(usd, "10");
-
7000 env(claw(gw, amount), ter(tecAMM_ACCOUNT));
-
7001 }
-
7002 }
-
7003
-
7004 void
-
7005 testAMMDepositWithFrozenAssets(FeatureBitset features)
-
7006 {
-
7007 testcase("test AMMDeposit with frozen assets");
-
7008 using namespace jtx;
-
7009
-
7010 // This lambda function is used to create trustlines
-
7011 // between gw and alice, and create an AMM account.
-
7012 // And also test the callback function.
-
7013 auto testAMMDeposit = [&](Env& env, std::function<void(AMM & amm)> cb) {
-
7014 env.fund(XRP(1'000), gw);
-
7015 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
-
7016 env.close();
-
7017 AMM amm(env, alice, XRP(100), USD(100), ter(tesSUCCESS));
-
7018 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
7019 cb(amm);
-
7020 };
-
7021
-
7022 // Deposit two assets, one of which is frozen,
-
7023 // then we should get tecFROZEN error.
-
7024 {
-
7025 Env env(*this, features);
-
7026 testAMMDeposit(env, [&](AMM& amm) {
-
7027 amm.deposit(
-
7028 alice,
-
7029 USD(100),
-
7030 XRP(100),
-
7031 std::nullopt,
-
7032 tfTwoAsset,
-
7033 ter(tecFROZEN));
-
7034 });
-
7035 }
-
7036
-
7037 // Deposit one asset, which is the frozen token,
-
7038 // then we should get tecFROZEN error.
-
7039 {
-
7040 Env env(*this, features);
-
7041 testAMMDeposit(env, [&](AMM& amm) {
-
7042 amm.deposit(
-
7043 alice,
-
7044 USD(100),
-
7045 std::nullopt,
-
7046 std::nullopt,
-
7047 tfSingleAsset,
-
7048 ter(tecFROZEN));
-
7049 });
-
7050 }
-
7051
-
7052 if (features[featureAMMClawback])
-
7053 {
-
7054 // Deposit one asset which is not the frozen token,
-
7055 // but the other asset is frozen. We should get tecFROZEN error
-
7056 // when feature AMMClawback is enabled.
-
7057 Env env(*this, features);
-
7058 testAMMDeposit(env, [&](AMM& amm) {
-
7059 amm.deposit(
-
7060 alice,
-
7061 XRP(100),
-
7062 std::nullopt,
-
7063 std::nullopt,
-
7064 tfSingleAsset,
-
7065 ter(tecFROZEN));
-
7066 });
-
7067 }
-
7068 else
-
7069 {
-
7070 // Deposit one asset which is not the frozen token,
-
7071 // but the other asset is frozen. We will get tecSUCCESS
-
7072 // when feature AMMClawback is not enabled.
-
7073 Env env(*this, features);
-
7074 testAMMDeposit(env, [&](AMM& amm) {
-
7075 amm.deposit(
-
7076 alice,
-
7077 XRP(100),
-
7078 std::nullopt,
-
7079 std::nullopt,
-
7080 tfSingleAsset,
-
7081 ter(tesSUCCESS));
-
7082 });
-
7083 }
-
7084 }
-
7085
-
7086 void
-
7087 testFixReserveCheckOnWithdrawal(FeatureBitset features)
-
7088 {
-
7089 testcase("Fix Reserve Check On Withdrawal");
-
7090 using namespace jtx;
-
7091
-
7092 auto const err = features[fixAMMv1_2] ? ter(tecINSUFFICIENT_RESERVE)
-
7093 : ter(tesSUCCESS);
-
7094
-
7095 auto test = [&](auto&& cb) {
-
7096 Env env(*this, features);
-
7097 auto const starting_xrp =
-
7098 reserve(env, 2) + env.current()->fees().base * 5;
-
7099 env.fund(starting_xrp, gw);
-
7100 env.fund(starting_xrp, alice);
-
7101 env.trust(USD(2'000), alice);
-
7102 env.close();
-
7103 env(pay(gw, alice, USD(2'000)));
-
7104 env.close();
-
7105 AMM amm(env, gw, EUR(1'000), USD(1'000));
-
7106 amm.deposit(alice, USD(1));
-
7107 cb(amm);
-
7108 };
-
7109
-
7110 // Equal withdraw
-
7111 test([&](AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); });
-
7112
-
7113 // Equal withdraw with a limit
-
7114 test([&](AMM& amm) {
-
7115 amm.withdraw(WithdrawArg{
-
7116 .account = alice,
-
7117 .asset1Out = EUR(0.1),
-
7118 .asset2Out = USD(0.1),
-
7119 .err = err});
+
6911 else
+
6912 {
+
6913 amm.withdrawAll(lp);
+
6914 BEAST_EXPECT(!amm.ammExists());
+
6915 }
+
6916 }
+
6917
+
6918 // More than one Liquidity Provider
+
6919 // XRP/IOU
+
6920 {
+
6921 Env env(*this, features);
+
6922 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)});
+
6923 AMM amm(env, gw, XRP(10), USD(10));
+
6924 amm.deposit(alice, 1'000);
+
6925 auto res =
+
6926 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
+
6927 BEAST_EXPECT(res && !res.value());
+
6928 res =
+
6929 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
+
6930 BEAST_EXPECT(res && !res.value());
+
6931 }
+
6932 // IOU/IOU, issuer of both IOU
+
6933 {
+
6934 Env env(*this, features);
+
6935 fund(env, gw, {alice}, XRP(1'000), {USD(1'000), EUR(1'000)});
+
6936 AMM amm(env, gw, EUR(10), USD(10));
+
6937 amm.deposit(alice, 1'000);
+
6938 auto res =
+
6939 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
+
6940 BEAST_EXPECT(res && !res.value());
+
6941 res =
+
6942 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
+
6943 BEAST_EXPECT(res && !res.value());
+
6944 }
+
6945 // IOU/IOU, issuer of one IOU
+
6946 {
+
6947 Env env(*this, features);
+
6948 Account const gw1("gw1");
+
6949 auto const YAN = gw1["YAN"];
+
6950 fund(env, gw, {gw1}, XRP(1'000), {USD(1'000)});
+
6951 fund(env, gw1, {gw}, XRP(1'000), {YAN(1'000)}, Fund::IOUOnly);
+
6952 AMM amm(env, gw1, YAN(10), USD(10));
+
6953 amm.deposit(gw, 1'000);
+
6954 auto res =
+
6955 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
+
6956 BEAST_EXPECT(res && !res.value());
+
6957 res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw1);
+
6958 BEAST_EXPECT(res && !res.value());
+
6959 }
+
6960 }
+
6961
+
6962 void
+
6963 testAMMClawback(FeatureBitset features)
+
6964 {
+
6965 testcase("test clawback from AMM account");
+
6966 using namespace jtx;
+
6967
+
6968 // Issuer has clawback enabled
+
6969 Env env(*this, features);
+
6970 env.fund(XRP(1'000), gw);
+
6971 env(fset(gw, asfAllowTrustLineClawback));
+
6972 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
+
6973 env.close();
+
6974
+
6975 // If featureAMMClawback is not enabled, AMMCreate is not allowed for
+
6976 // clawback-enabled issuer
+
6977 if (!features[featureAMMClawback])
+
6978 {
+
6979 AMM amm(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
+
6980 AMM amm1(env, alice, USD(100), XRP(100), ter(tecNO_PERMISSION));
+
6981 env(fclear(gw, asfAllowTrustLineClawback));
+
6982 env.close();
+
6983 // Can't be cleared
+
6984 AMM amm2(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
+
6985 }
+
6986 // If featureAMMClawback is enabled, AMMCreate is allowed for
+
6987 // clawback-enabled issuer. Clawback from the AMM Account is not
+
6988 // allowed, which will return tecAMM_ACCOUNT. We can only use
+
6989 // AMMClawback transaction to claw back from AMM Account.
+
6990 else
+
6991 {
+
6992 AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
+
6993 AMM amm1(env, alice, USD(100), XRP(200), ter(tecDUPLICATE));
+
6994
+
6995 // Construct the amount being clawed back using AMM account.
+
6996 // By doing this, we make the clawback transaction's Amount field's
+
6997 // subfield `issuer` to be the AMM account, which means
+
6998 // we are clawing back from an AMM account. This should return an
+
6999 // tecAMM_ACCOUNT error because regular Clawback transaction is not
+
7000 // allowed for clawing back from an AMM account. Please notice the
+
7001 // `issuer` subfield represents the account being clawed back, which
+
7002 // is confusing.
+
7003 Issue usd(USD.issue().currency, amm.ammAccount());
+
7004 auto amount = amountFromString(usd, "10");
+
7005 env(claw(gw, amount), ter(tecAMM_ACCOUNT));
+
7006 }
+
7007 }
+
7008
+
7009 void
+
7010 testAMMDepositWithFrozenAssets(FeatureBitset features)
+
7011 {
+
7012 testcase("test AMMDeposit with frozen assets");
+
7013 using namespace jtx;
+
7014
+
7015 // This lambda function is used to create trustlines
+
7016 // between gw and alice, and create an AMM account.
+
7017 // And also test the callback function.
+
7018 auto testAMMDeposit = [&](Env& env, std::function<void(AMM & amm)> cb) {
+
7019 env.fund(XRP(1'000), gw);
+
7020 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
+
7021 env.close();
+
7022 AMM amm(env, alice, XRP(100), USD(100), ter(tesSUCCESS));
+
7023 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
7024 cb(amm);
+
7025 };
+
7026
+
7027 // Deposit two assets, one of which is frozen,
+
7028 // then we should get tecFROZEN error.
+
7029 {
+
7030 Env env(*this, features);
+
7031 testAMMDeposit(env, [&](AMM& amm) {
+
7032 amm.deposit(
+
7033 alice,
+
7034 USD(100),
+
7035 XRP(100),
+
7036 std::nullopt,
+
7037 tfTwoAsset,
+
7038 ter(tecFROZEN));
+
7039 });
+
7040 }
+
7041
+
7042 // Deposit one asset, which is the frozen token,
+
7043 // then we should get tecFROZEN error.
+
7044 {
+
7045 Env env(*this, features);
+
7046 testAMMDeposit(env, [&](AMM& amm) {
+
7047 amm.deposit(
+
7048 alice,
+
7049 USD(100),
+
7050 std::nullopt,
+
7051 std::nullopt,
+
7052 tfSingleAsset,
+
7053 ter(tecFROZEN));
+
7054 });
+
7055 }
+
7056
+
7057 if (features[featureAMMClawback])
+
7058 {
+
7059 // Deposit one asset which is not the frozen token,
+
7060 // but the other asset is frozen. We should get tecFROZEN error
+
7061 // when feature AMMClawback is enabled.
+
7062 Env env(*this, features);
+
7063 testAMMDeposit(env, [&](AMM& amm) {
+
7064 amm.deposit(
+
7065 alice,
+
7066 XRP(100),
+
7067 std::nullopt,
+
7068 std::nullopt,
+
7069 tfSingleAsset,
+
7070 ter(tecFROZEN));
+
7071 });
+
7072 }
+
7073 else
+
7074 {
+
7075 // Deposit one asset which is not the frozen token,
+
7076 // but the other asset is frozen. We will get tecSUCCESS
+
7077 // when feature AMMClawback is not enabled.
+
7078 Env env(*this, features);
+
7079 testAMMDeposit(env, [&](AMM& amm) {
+
7080 amm.deposit(
+
7081 alice,
+
7082 XRP(100),
+
7083 std::nullopt,
+
7084 std::nullopt,
+
7085 tfSingleAsset,
+
7086 ter(tesSUCCESS));
+
7087 });
+
7088 }
+
7089 }
+
7090
+
7091 void
+
7092 testFixReserveCheckOnWithdrawal(FeatureBitset features)
+
7093 {
+
7094 testcase("Fix Reserve Check On Withdrawal");
+
7095 using namespace jtx;
+
7096
+
7097 auto const err = features[fixAMMv1_2] ? ter(tecINSUFFICIENT_RESERVE)
+
7098 : ter(tesSUCCESS);
+
7099
+
7100 auto test = [&](auto&& cb) {
+
7101 Env env(*this, features);
+
7102 auto const starting_xrp =
+
7103 reserve(env, 2) + env.current()->fees().base * 5;
+
7104 env.fund(starting_xrp, gw);
+
7105 env.fund(starting_xrp, alice);
+
7106 env.trust(USD(2'000), alice);
+
7107 env.close();
+
7108 env(pay(gw, alice, USD(2'000)));
+
7109 env.close();
+
7110 AMM amm(env, gw, EUR(1'000), USD(1'000));
+
7111 amm.deposit(alice, USD(1));
+
7112 cb(amm);
+
7113 };
+
7114
+
7115 // Equal withdraw
+
7116 test([&](AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); });
+
7117
+
7118 // Equal withdraw with a limit
+
7119 test([&](AMM& amm) {
7120 amm.withdraw(WithdrawArg{
7121 .account = alice,
-
7122 .asset1Out = USD(0.1),
-
7123 .asset2Out = EUR(0.1),
+
7122 .asset1Out = EUR(0.1),
+
7123 .asset2Out = USD(0.1),
7124 .err = err});
-
7125 });
-
7126
-
7127 // Single withdraw
-
7128 test([&](AMM& amm) {
-
7129 amm.withdraw(WithdrawArg{
-
7130 .account = alice, .asset1Out = EUR(0.1), .err = err});
-
7131 amm.withdraw(WithdrawArg{.account = alice, .asset1Out = USD(0.1)});
-
7132 });
-
7133 }
-
7134
-
7135 void
-
7136 run() override
-
7137 {
-
7138 FeatureBitset const all{jtx::supported_amendments()};
-
7139 testInvalidInstance();
-
7140 testInstanceCreate();
-
7141 testInvalidDeposit(all);
-
7142 testInvalidDeposit(all - featureAMMClawback);
-
7143 testDeposit();
-
7144 testInvalidWithdraw();
-
7145 testWithdraw();
-
7146 testInvalidFeeVote();
-
7147 testFeeVote();
-
7148 testInvalidBid();
-
7149 testBid(all);
-
7150 testBid(all - fixAMMv1_1);
-
7151 testInvalidAMMPayment();
-
7152 testBasicPaymentEngine(all);
-
7153 testBasicPaymentEngine(all - fixAMMv1_1);
-
7154 testBasicPaymentEngine(all - fixReducedOffersV2);
-
7155 testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2);
-
7156 testAMMTokens();
-
7157 testAmendment();
-
7158 testFlags();
-
7159 testRippling();
-
7160 testAMMAndCLOB(all);
-
7161 testAMMAndCLOB(all - fixAMMv1_1);
-
7162 testTradingFee(all);
-
7163 testTradingFee(all - fixAMMv1_1);
-
7164 testAdjustedTokens(all);
-
7165 testAdjustedTokens(all - fixAMMv1_1);
-
7166 testAutoDelete();
-
7167 testClawback();
-
7168 testAMMID();
-
7169 testSelection(all);
-
7170 testSelection(all - fixAMMv1_1);
-
7171 testFixDefaultInnerObj();
-
7172 testMalformed();
-
7173 testFixOverflowOffer(all);
-
7174 testFixOverflowOffer(all - fixAMMv1_1);
-
7175 testSwapRounding();
-
7176 testFixChangeSpotPriceQuality(all);
-
7177 testFixChangeSpotPriceQuality(all - fixAMMv1_1);
-
7178 testFixAMMOfferBlockedByLOB(all);
-
7179 testFixAMMOfferBlockedByLOB(all - fixAMMv1_1);
-
7180 testLPTokenBalance(all);
-
7181 testLPTokenBalance(all - fixAMMv1_1);
-
7182 testAMMClawback(all);
-
7183 testAMMClawback(all - featureAMMClawback);
-
7184 testAMMClawback(all - fixAMMv1_1 - featureAMMClawback);
-
7185 testAMMDepositWithFrozenAssets(all);
-
7186 testAMMDepositWithFrozenAssets(all - featureAMMClawback);
-
7187 testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
-
7188 testFixReserveCheckOnWithdrawal(all);
-
7189 testFixReserveCheckOnWithdrawal(all - fixAMMv1_2);
-
7190 }
-
7191};
-
7192
-
7193BEAST_DEFINE_TESTSUITE_PRIO(AMM, app, ripple, 1);
-
7194
-
7195} // namespace test
-
7196} // namespace ripple
+
7125 amm.withdraw(WithdrawArg{
+
7126 .account = alice,
+
7127 .asset1Out = USD(0.1),
+
7128 .asset2Out = EUR(0.1),
+
7129 .err = err});
+
7130 });
+
7131
+
7132 // Single withdraw
+
7133 test([&](AMM& amm) {
+
7134 amm.withdraw(WithdrawArg{
+
7135 .account = alice, .asset1Out = EUR(0.1), .err = err});
+
7136 amm.withdraw(WithdrawArg{.account = alice, .asset1Out = USD(0.1)});
+
7137 });
+
7138 }
+
7139
+
7140 void
+
7141 run() override
+
7142 {
+
7143 FeatureBitset const all{jtx::supported_amendments()};
+
7144 testInvalidInstance();
+
7145 testInstanceCreate();
+
7146 testInvalidDeposit(all);
+
7147 testInvalidDeposit(all - featureAMMClawback);
+
7148 testDeposit();
+
7149 testInvalidWithdraw();
+
7150 testWithdraw();
+
7151 testInvalidFeeVote();
+
7152 testFeeVote();
+
7153 testInvalidBid();
+
7154 testBid(all);
+
7155 testBid(all - fixAMMv1_1);
+
7156 testInvalidAMMPayment();
+
7157 testBasicPaymentEngine(all);
+
7158 testBasicPaymentEngine(all - fixAMMv1_1);
+
7159 testBasicPaymentEngine(all - fixReducedOffersV2);
+
7160 testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2);
+
7161 testAMMTokens();
+
7162 testAmendment();
+
7163 testFlags();
+
7164 testRippling();
+
7165 testAMMAndCLOB(all);
+
7166 testAMMAndCLOB(all - fixAMMv1_1);
+
7167 testTradingFee(all);
+
7168 testTradingFee(all - fixAMMv1_1);
+
7169 testAdjustedTokens(all);
+
7170 testAdjustedTokens(all - fixAMMv1_1);
+
7171 testAutoDelete();
+
7172 testClawback();
+
7173 testAMMID();
+
7174 testSelection(all);
+
7175 testSelection(all - fixAMMv1_1);
+
7176 testFixDefaultInnerObj();
+
7177 testMalformed();
+
7178 testFixOverflowOffer(all);
+
7179 testFixOverflowOffer(all - fixAMMv1_1);
+
7180 testSwapRounding();
+
7181 testFixChangeSpotPriceQuality(all);
+
7182 testFixChangeSpotPriceQuality(all - fixAMMv1_1);
+
7183 testFixAMMOfferBlockedByLOB(all);
+
7184 testFixAMMOfferBlockedByLOB(all - fixAMMv1_1);
+
7185 testLPTokenBalance(all);
+
7186 testLPTokenBalance(all - fixAMMv1_1);
+
7187 testAMMClawback(all);
+
7188 testAMMClawback(all - featureAMMClawback);
+
7189 testAMMClawback(all - fixAMMv1_1 - featureAMMClawback);
+
7190 testAMMDepositWithFrozenAssets(all);
+
7191 testAMMDepositWithFrozenAssets(all - featureAMMClawback);
+
7192 testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
+
7193 testFixReserveCheckOnWithdrawal(all);
+
7194 testFixReserveCheckOnWithdrawal(all - fixAMMv1_2);
+
7195 }
+
7196};
+
7197
+
7198BEAST_DEFINE_TESTSUITE_PRIO(AMM, app, ripple, 1);
+
7199
+
7200} // namespace test
+
7201} // namespace ripple
Represents a JSON value.
Definition: json_value.h:148
@@ -7322,13 +7327,13 @@ $(function() {
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
A transaction testing environment.
Definition: Env.h:120
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:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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
Account const & master
Definition: Env.h:124
-
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:281
+
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:283
beast::Journal const journal
Definition: Env.h:161
-
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:767
+
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:769
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
@@ -7451,41 +7456,41 @@ $(function() {
Zero allows classes to offer efficient comparisons to zero.
Definition: Zero.h:43
@ none
Definition: STBase.h:44
-
Basic tests of AMM that do not use offers.
Definition: AMM_test.cpp:47
-
void testBid(FeatureBitset features)
Definition: AMM_test.cpp:2838
-
void testRippling()
Definition: AMM_test.cpp:4641
-
void testAMMTokens()
Definition: AMM_test.cpp:4481
-
void testInvalidDeposit(FeatureBitset features)
Definition: AMM_test.cpp:418
-
void testAMMID()
Definition: AMM_test.cpp:5441
-
void testFeeVote()
Definition: AMM_test.cpp:2508
-
void testWithdraw()
Definition: AMM_test.cpp:2121
-
void testInvalidAMMPayment()
Definition: AMM_test.cpp:3349
-
void testSelection(FeatureBitset features)
Definition: AMM_test.cpp:5494
-
void testAMMClawback(FeatureBitset features)
Definition: AMM_test.cpp:6958
-
void testInvalidFeeVote()
Definition: AMM_test.cpp:2439
-
void testSwapRounding()
Definition: AMM_test.cpp:6675
-
void testLPTokenBalance(FeatureBitset features)
Definition: AMM_test.cpp:6840
-
void testDeposit()
Definition: AMM_test.cpp:1303
-
void run() override
Runs the suite.
Definition: AMM_test.cpp:7136
-
void testInstanceCreate()
Definition: AMM_test.cpp:50
-
void testTradingFee(FeatureBitset features)
Definition: AMM_test.cpp:4762
-
void testFixOverflowOffer(FeatureBitset features)
Definition: AMM_test.cpp:6352
-
void testInvalidWithdraw()
Definition: AMM_test.cpp:1603
-
void testAMMAndCLOB(FeatureBitset features)
Definition: AMM_test.cpp:4691
-
void testInvalidInstance()
Definition: AMM_test.cpp:135
-
void testMalformed()
Definition: AMM_test.cpp:6293
-
void testAutoDelete()
Definition: AMM_test.cpp:5316
-
void testBasicPaymentEngine(FeatureBitset features)
Definition: AMM_test.cpp:3512
-
void testInvalidBid()
Definition: AMM_test.cpp:2613
-
void testFixChangeSpotPriceQuality(FeatureBitset features)
Definition: AMM_test.cpp:6077
-
void testAmendment()
Definition: AMM_test.cpp:4592
-
void testClawback()
Definition: AMM_test.cpp:5429
-
void testFixDefaultInnerObj()
Definition: AMM_test.cpp:5977
-
void testFixReserveCheckOnWithdrawal(FeatureBitset features)
Definition: AMM_test.cpp:7087
-
void testAdjustedTokens(FeatureBitset features)
Definition: AMM_test.cpp:5151
-
void testFlags()
Definition: AMM_test.cpp:4620
-
void testAMMDepositWithFrozenAssets(FeatureBitset features)
Definition: AMM_test.cpp:7005
-
void testFixAMMOfferBlockedByLOB(FeatureBitset features)
Definition: AMM_test.cpp:6712
+
Basic tests of AMM that do not use offers.
Definition: AMM_test.cpp:48
+
void testBid(FeatureBitset features)
Definition: AMM_test.cpp:2839
+
void testRippling()
Definition: AMM_test.cpp:4642
+
void testAMMTokens()
Definition: AMM_test.cpp:4482
+
void testInvalidDeposit(FeatureBitset features)
Definition: AMM_test.cpp:419
+
void testAMMID()
Definition: AMM_test.cpp:5442
+
void testFeeVote()
Definition: AMM_test.cpp:2509
+
void testWithdraw()
Definition: AMM_test.cpp:2122
+
void testInvalidAMMPayment()
Definition: AMM_test.cpp:3350
+
void testSelection(FeatureBitset features)
Definition: AMM_test.cpp:5495
+
void testAMMClawback(FeatureBitset features)
Definition: AMM_test.cpp:6963
+
void testInvalidFeeVote()
Definition: AMM_test.cpp:2440
+
void testSwapRounding()
Definition: AMM_test.cpp:6680
+
void testLPTokenBalance(FeatureBitset features)
Definition: AMM_test.cpp:6845
+
void testDeposit()
Definition: AMM_test.cpp:1304
+
void run() override
Runs the suite.
Definition: AMM_test.cpp:7141
+
void testInstanceCreate()
Definition: AMM_test.cpp:51
+
void testTradingFee(FeatureBitset features)
Definition: AMM_test.cpp:4763
+
void testFixOverflowOffer(FeatureBitset features)
Definition: AMM_test.cpp:6355
+
void testInvalidWithdraw()
Definition: AMM_test.cpp:1604
+
void testAMMAndCLOB(FeatureBitset features)
Definition: AMM_test.cpp:4692
+
void testInvalidInstance()
Definition: AMM_test.cpp:136
+
void testMalformed()
Definition: AMM_test.cpp:6296
+
void testAutoDelete()
Definition: AMM_test.cpp:5317
+
void testBasicPaymentEngine(FeatureBitset features)
Definition: AMM_test.cpp:3513
+
void testInvalidBid()
Definition: AMM_test.cpp:2614
+
void testFixChangeSpotPriceQuality(FeatureBitset features)
Definition: AMM_test.cpp:6078
+
void testAmendment()
Definition: AMM_test.cpp:4593
+
void testClawback()
Definition: AMM_test.cpp:5430
+
void testFixDefaultInnerObj()
Definition: AMM_test.cpp:5978
+
void testFixReserveCheckOnWithdrawal(FeatureBitset features)
Definition: AMM_test.cpp:7092
+
void testAdjustedTokens(FeatureBitset features)
Definition: AMM_test.cpp:5152
+
void testFlags()
Definition: AMM_test.cpp:4621
+
void testAMMDepositWithFrozenAssets(FeatureBitset features)
Definition: AMM_test.cpp:7010
+
void testFixAMMOfferBlockedByLOB(FeatureBitset features)
Definition: AMM_test.cpp:6717
Definition: AMM.h:75
std::optional< Account > account
Definition: AMM.h:76
std::optional< STAmount > asset1In
Definition: AMM.h:78
diff --git a/AccountDelete__test_8cpp_source.html b/AccountDelete__test_8cpp_source.html index 429c72a923..297f129f9e 100644 --- a/AccountDelete__test_8cpp_source.html +++ b/AccountDelete__test_8cpp_source.html @@ -1411,9 +1411,9 @@ $(function() {
A transaction testing environment.
Definition: Env.h:120
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:212
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:455
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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:767
+
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:769
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
A balance matches.
Definition: balance.h:39
Definition: credentials.h:53
@@ -1438,12 +1438,12 @@ $(function() {
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition: Indexes.cpp:386
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Definition: TestHelpers.h:441
Json::Value cancel(jtx::Account const &dest, uint256 const &checkId)
Cancel a check.
Definition: check.cpp:62
-
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:32
-
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:50
-
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:83
+
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:32
+
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:50
+
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:83
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition: deposit.cpp:32
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition: deposit.cpp:54
-
Json::Value setValid(jtx::Account const &account)
Definition: did.cpp:43
+
Json::Value setValid(jtx::Account const &account)
Definition: dids.cpp:43
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition: ticket.cpp:31
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition: regkey.cpp:29
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition: multisign.cpp:34
diff --git a/AccountInfo__test_8cpp_source.html b/AccountInfo__test_8cpp_source.html index e093bcc19a..7a80e22481 100644 --- a/AccountInfo__test_8cpp_source.html +++ b/AccountInfo__test_8cpp_source.html @@ -787,7 +787,7 @@ $(function() {
void testErrors()
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:120
-
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:767
+
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:769
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 beb2f54983..8215fa696b 100644 --- a/AccountLines__test_8cpp_source.html +++ b/AccountLines__test_8cpp_source.html @@ -1677,7 +1677,7 @@ $(function() {
@ nullValue
'null' value
Definition: json_value.h:37
@ arrayValue
array value (ordered list)
Definition: json_value.h:43
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
-
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:185
+
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:186
std::string expected_field_message(std::string const &name, std::string const &type)
Definition: ErrorCodes.h:327
Json::Value missing_field_error(std::string const &name)
Definition: ErrorCodes.h:273
Keylet escrow(AccountID const &src, std::uint32_t seq) noexcept
An escrow entry.
Definition: Indexes.cpp:380
diff --git a/AccountObjects__test_8cpp_source.html b/AccountObjects__test_8cpp_source.html index 5bef8a8e8f..2a8a62f12a 100644 --- a/AccountObjects__test_8cpp_source.html +++ b/AccountObjects__test_8cpp_source.html @@ -1563,8 +1563,8 @@ $(function() {
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:212
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:281
-
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:767
+
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:283
+
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:769
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:113
diff --git a/AccountOffers__test_8cpp_source.html b/AccountOffers__test_8cpp_source.html index 8ccfd78c9e..d4ca0e245a 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:120
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:767
+
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:769
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:37
diff --git a/AccountTxPaging_8cpp_source.html b/AccountTxPaging_8cpp_source.html index 6f3e9d813d..41805bf705 100644 --- a/AccountTxPaging_8cpp_source.html +++ b/AccountTxPaging_8cpp_source.html @@ -119,33 +119,42 @@ $(function() {
41
42 auto tr = std::make_shared<Transaction>(txn, reason, app);
43
-
44 tr->setStatus(Transaction::sqlTransactionStatus(status));
-
45 tr->setLedger(ledger_index);
+
44 auto metaset =
+
45 std::make_shared<TxMeta>(tr->getID(), tr->getLedger(), rawMeta);
46
-
47 auto metaset =
-
48 std::make_shared<TxMeta>(tr->getID(), tr->getLedger(), rawMeta);
-
49
-
50 to.emplace_back(std::move(tr), metaset);
-
51};
-
52
-
53void
-
54saveLedgerAsync(Application& app, std::uint32_t seq)
-
55{
-
56 if (auto l = app.getLedgerMaster().getLedgerBySeq(seq))
-
57 pendSaveValidated(app, l, false, false);
-
58}
+
47 // if properly formed meta is available we can use it to generate ctid
+
48 if (metaset->getAsObject().isFieldPresent(sfTransactionIndex))
+
49 tr->setStatus(
+
50 Transaction::sqlTransactionStatus(status),
+
51 ledger_index,
+
52 metaset->getAsObject().getFieldU32(sfTransactionIndex),
+
53 app.config().NETWORK_ID);
+
54 else
+
55 tr->setStatus(Transaction::sqlTransactionStatus(status), ledger_index);
+
56
+
57 to.emplace_back(std::move(tr), metaset);
+
58};
59
-
60} // namespace ripple
+
60void
+
61saveLedgerAsync(Application& app, std::uint32_t seq)
+
62{
+
63 if (auto l = app.getLedgerMaster().getLedgerBySeq(seq))
+
64 pendSaveValidated(app, l, false, false);
+
65}
+
66
+
67} // namespace ripple
Definition: Application.h:114
+
virtual Config & config()=0
virtual LedgerMaster & getLedgerMaster()=0
+
uint32_t NETWORK_ID
Definition: Config.h:156
std::shared_ptr< Ledger const > getLedgerBySeq(std::uint32_t index)
Definition: Serializer.h:347
-
static TransStatus sqlTransactionStatus(boost::optional< std::string > const &status)
Definition: Transaction.cpp:64
+
static TransStatus sqlTransactionStatus(boost::optional< std::string > const &status)
Definition: Transaction.cpp:73
T emplace_back(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
-
void saveLedgerAsync(Application &app, std::uint32_t seq)
+
void saveLedgerAsync(Application &app, std::uint32_t seq)
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition: Slice.h:244
void convertBlobsToTxResult(RelationalDatabase::AccountTxs &to, std::uint32_t ledger_index, std::string const &status, Blob const &rawTxn, Blob const &rawMeta, Application &app)
bool pendSaveValidated(Application &app, std::shared_ptr< Ledger const > const &ledger, bool isSynchronous, bool isCurrent)
Save, or arrange to save, a fully-validated ledger Returns false on error.
Definition: Ledger.cpp:993
diff --git a/AccountTxPaging_8h_source.html b/AccountTxPaging_8h_source.html index cb90f574cf..04e3ffa993 100644 --- a/AccountTxPaging_8h_source.html +++ b/AccountTxPaging_8h_source.html @@ -126,7 +126,7 @@ $(function() {
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
-
void saveLedgerAsync(Application &app, std::uint32_t seq)
+
void saveLedgerAsync(Application &app, std::uint32_t seq)
void convertBlobsToTxResult(RelationalDatabase::AccountTxs &to, std::uint32_t ledger_index, std::string const &status, Blob const &rawTxn, Blob const &rawMeta, Application &app)
std::vector< unsigned char > Blob
Storage for linear binary data.
Definition: Blob.h:30
diff --git a/AccountTxPaging__test_8cpp_source.html b/AccountTxPaging__test_8cpp_source.html index a2897325b2..a9609bc51c 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:120
-
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:767
+
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:769
@ nullValue
'null' value
Definition: json_value.h:37
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 92c9dd80af..8ff99abb68 100644 --- a/AccountTx__test_8cpp_source.html +++ b/AccountTx__test_8cpp_source.html @@ -869,10 +869,10 @@ $(function() {
A transaction testing environment.
Definition: Env.h:120
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition: Env.cpp:111
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:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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:281
-
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:767
+
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:283
+
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:769
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
@@ -887,7 +887,7 @@ $(function() {
@ nullValue
'null' value
Definition: json_value.h:37
@ arrayValue
array value (ordered list)
Definition: json_value.h:43
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
-
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:177
+
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:178
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:175
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition: Indexes.cpp:327
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition: Indexes.cpp:386
diff --git a/AmendmentTable__test_8cpp_source.html b/AmendmentTable__test_8cpp_source.html index 4b3039226f..02f266d47b 100644 --- a/AmendmentTable__test_8cpp_source.html +++ b/AmendmentTable__test_8cpp_source.html @@ -1453,7 +1453,7 @@ $(function() {
static constexpr std::size_t size()
Definition: base_uint.h:526
Definition: SuiteJournal.h:101
A transaction testing environment.
Definition: Env.h:120
-
Application & app()
Definition: Env.h:258
+
Application & app()
Definition: Env.h:260
T copy(T... args)
T count(T... args)
diff --git a/Application_8cpp_source.html b/Application_8cpp_source.html index 2b5ce191ab..9886f857c6 100644 --- a/Application_8cpp_source.html +++ b/Application_8cpp_source.html @@ -2563,7 +2563,7 @@ $(function() {
std::unique_ptr< Application > make_Application(std::unique_ptr< Config > config, std::unique_ptr< Logs > logs, std::unique_ptr< TimeKeeper > timeKeeper)
std::unique_ptr< ServerHandler > make_ServerHandler(Application &app, boost::asio::io_service &io_service, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
void initAccountIdCache(std::size_t count)
Initialize the global cache used to map AccountID to base58 conversions.
Definition: AccountID.cpp:107
-
std::unique_ptr< NetworkOPs > make_NetworkOPs(Application &app, NetworkOPs::clock_type &clock, bool standalone, std::size_t minPeerCount, bool startvalid, JobQueue &job_queue, LedgerMaster &ledgerMaster, ValidatorKeys const &validatorKeys, boost::asio::io_service &io_svc, beast::Journal journal, beast::insight::Collector::ptr const &collector)
+
std::unique_ptr< NetworkOPs > make_NetworkOPs(Application &app, NetworkOPs::clock_type &clock, bool standalone, std::size_t minPeerCount, bool startvalid, JobQueue &job_queue, LedgerMaster &ledgerMaster, ValidatorKeys const &validatorKeys, boost::asio::io_service &io_svc, beast::Journal journal, beast::insight::Collector::ptr const &collector)
std::unordered_map< std::string, boost::asio::ip::tcp::endpoint > Endpoints
Definition: ServerImpl.h:39
@ hotACCOUNT_NODE
Definition: NodeObject.h:35
std::pair< PublicKey, SecretKey > getNodeIdentity(Application &app, boost::program_options::variables_map const &cmdline)
The cryptographic credentials identifying this server instance.
@@ -2583,9 +2583,9 @@ $(function() {
create_genesis_t const create_genesis
Definition: Ledger.cpp:51
static void fixConfigPorts(Config &config, Endpoints const &endpoints)
std::unique_ptr< AmendmentTable > make_AmendmentTable(Application &app, std::chrono::seconds majorityTime, std::vector< AmendmentTable::FeatureInfo > const &supported, Section const &enabled, Section const &vetoed, beast::Journal journal)
-
Overlay::Setup setup_Overlay(BasicConfig const &config)
+
Overlay::Setup setup_Overlay(BasicConfig const &config)
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Definition: contract.cpp:37
-
std::unique_ptr< Overlay > make_Overlay(Application &app, Overlay::Setup const &setup, ServerHandler &serverHandler, Resource::Manager &resourceManager, Resolver &resolver, boost::asio::io_service &io_service, BasicConfig const &config, beast::insight::Collector::ptr const &collector)
Creates the implementation of Overlay.
+
std::unique_ptr< Overlay > make_Overlay(Application &app, Overlay::Setup const &setup, ServerHandler &serverHandler, Resource::Manager &resourceManager, Resolver &resolver, boost::asio::io_service &io_service, BasicConfig const &config, beast::insight::Collector::ptr const &collector)
Creates the implementation of Overlay.
std::unique_ptr< SHAMapStore > make_SHAMapStore(Application &app, NodeStore::Scheduler &scheduler, beast::Journal journal)
STL namespace.
diff --git a/ApplyContext_8cpp_source.html b/ApplyContext_8cpp_source.html index 72827243e7..031f0d8728 100644 --- a/ApplyContext_8cpp_source.html +++ b/ApplyContext_8cpp_source.html @@ -180,8 +180,8 @@ $(function() {
102 uint256 const& index,
103 bool isDelete,
104 std::shared_ptr<SLE const> const& before,
-
105 std::shared_ptr<SLE const> const& after) {
-
106 (..., std::get<Is>(checkers).visitEntry(isDelete, before, after));
+
105 std::shared_ptr<SLE const> const& after) {
+
106 (..., std::get<Is>(checkers).visitEntry(isDelete, before, after));
107 });
108
109 // Note: do not replace this logic with a `...&&` fold expression.
@@ -266,10 +266,10 @@ $(function() {
@ tefINVARIANT_FAILED
Definition: TER.h:183
@ tecINVARIANT_FAILED
Definition: TER.h:300
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:2127
InvariantChecks getInvariantChecks()
get a tuple of all invariant checks
ApplyFlags
Definition: ApplyView.h:31
@ tapDRY_RUN
Definition: ApplyView.h:47
-
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: Escrow.cpp:86
diff --git a/ApplyContext_8h_source.html b/ApplyContext_8h_source.html index fb20de8e33..a526747cb2 100644 --- a/ApplyContext_8h_source.html +++ b/ApplyContext_8h_source.html @@ -172,7 +172,7 @@ $(function() {
100 uint256 const& key,
101 bool isDelete,
102 std::shared_ptr<SLE const> const& before,
-
103 std::shared_ptr<SLE const> const& after)> const& func);
+
103 std::shared_ptr<SLE const> const& after)> const& func);
104
105 void
106 destroyXRP(XRPAmount const& fee)
@@ -238,8 +238,8 @@ $(function() {
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: View.cpp:2127
ApplyFlags
Definition: ApplyView.h:31
-
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: Escrow.cpp:86
diff --git a/ApplyStateTable_8cpp_source.html b/ApplyStateTable_8cpp_source.html index 0112921b76..233de823ed 100644 --- a/ApplyStateTable_8cpp_source.html +++ b/ApplyStateTable_8cpp_source.html @@ -156,7 +156,7 @@ $(function() {
78 uint256 const& key,
79 bool isDelete,
80 std::shared_ptr<SLE const> const& before,
-
81 std::shared_ptr<SLE const> const& after)> const& func) const
+
81 std::shared_ptr<SLE const> const& after)> const& func) const
82{
83 for (auto& item : items_)
84 {
@@ -850,7 +850,7 @@ $(function() {
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:114
LedgerEntryType
Identifiers for on-ledger objects.
Definition: LedgerFormats.h:54
-
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: Escrow.cpp:86
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: View.cpp:2127
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Definition: contract.cpp:37
STL namespace.
diff --git a/ApplyStateTable_8h_source.html b/ApplyStateTable_8h_source.html index f1b6bfedd6..797e2382ee 100644 --- a/ApplyStateTable_8h_source.html +++ b/ApplyStateTable_8h_source.html @@ -178,7 +178,7 @@ $(function() {
100 uint256 const& key,
101 bool isDelete,
102 std::shared_ptr<SLE const> const& before,
-
103 std::shared_ptr<SLE const> const& after)> const& func) const;
+
103 std::shared_ptr<SLE const> const& after)> const& func) const;
104
105 void
106 erase(ReadView const& base, std::shared_ptr<SLE> const& sle);
@@ -282,7 +282,7 @@ $(function() {
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
-
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: Escrow.cpp:86
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: View.cpp:2127
diff --git a/ApplyViewImpl_8cpp_source.html b/ApplyViewImpl_8cpp_source.html index 2beae407b3..3ec95f8af1 100644 --- a/ApplyViewImpl_8cpp_source.html +++ b/ApplyViewImpl_8cpp_source.html @@ -128,7 +128,7 @@ $(function() {
50 uint256 const& key,
51 bool isDelete,
52 std::shared_ptr<SLE const> const& before,
-
53 std::shared_ptr<SLE const> const& after)> const& func)
+
53 std::shared_ptr<SLE const> const& after)> const& func)
54{
55 items_.visit(to, func);
56}
@@ -151,8 +151,8 @@ $(function() {
detail::ApplyStateTable items_
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: View.cpp:2127
ApplyFlags
Definition: ApplyView.h:31
-
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: Escrow.cpp:86
diff --git a/ApplyViewImpl_8h_source.html b/ApplyViewImpl_8h_source.html index ba63e98062..394d7cd339 100644 --- a/ApplyViewImpl_8h_source.html +++ b/ApplyViewImpl_8h_source.html @@ -143,7 +143,7 @@ $(function() {
88 uint256 const& key,
89 bool isDelete,
90 std::shared_ptr<SLE const> const& before,
-
91 std::shared_ptr<SLE const> const& after)> const& func);
+
91 std::shared_ptr<SLE const> const& after)> const& func);
92
93private:
94 std::optional<STAmount> deliver_;
@@ -174,8 +174,8 @@ $(function() {
ApplyFlags flags() const override
Returns the tx apply flags.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: View.cpp:2127
ApplyFlags
Definition: ApplyView.h:31
-
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: Escrow.cpp:86
diff --git a/BatchWriter_8cpp_source.html b/BatchWriter_8cpp_source.html index 9f5d9f2a90..3188358f44 100644 --- a/BatchWriter_8cpp_source.html +++ b/BatchWriter_8cpp_source.html @@ -106,7 +106,7 @@ $(function() {
28 , mWriteLoad(0)
29 , mWritePending(false)
30{
-
31 mWriteSet.reserve(batchWritePreallocationSize);
+
31 mWriteSet.reserve(batchWritePreallocationSize);
32}
33
34BatchWriter::~BatchWriter()
@@ -121,7 +121,7 @@ $(function() {
43
44 // If the batch has reached its limit, we wait
45 // until the batch writer is finished
-
46 while (mWriteSet.size() >= batchWriteLimitSize)
+
46 while (mWriteSet.size() >= batchWriteLimitSize)
47 mWriteCondition.wait(sl);
48
49 mWriteSet.push_back(object);
@@ -155,7 +155,7 @@ $(function() {
77 {
78 std::vector<std::shared_ptr<NodeObject>> set;
79
-
80 set.reserve(batchWritePreallocationSize);
+
80 set.reserve(batchWritePreallocationSize);
81
82 {
83 std::lock_guard sl(mWriteMutex);
@@ -220,8 +220,8 @@ $(function() {
T empty(T... args)
T max(T... args)
-
@ batchWriteLimitSize
-
@ batchWritePreallocationSize
+
@ batchWriteLimitSize
+
@ batchWritePreallocationSize
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
Definition: BasicConfig.h:315
T notify_all(T... args)
diff --git a/BookChanges__test_8cpp_source.html b/BookChanges__test_8cpp_source.html index be02aa76ea..35d880822d 100644 --- a/BookChanges__test_8cpp_source.html +++ b/BookChanges__test_8cpp_source.html @@ -184,7 +184,7 @@ $(function() {
void testConventionalLedgerInputStrings()
void testLedgerInputDefaultBehavior()
A transaction testing environment.
Definition: Env.h:120
-
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:767
+
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:769
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
diff --git a/BookDirs__test_8cpp_source.html b/BookDirs__test_8cpp_source.html index 95203b563e..9e3730430b 100644 --- a/BookDirs__test_8cpp_source.html +++ b/BookDirs__test_8cpp_source.html @@ -195,7 +195,7 @@ $(function() {
Definition: Feature.h:147
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:120
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
diff --git a/BookOffers_8cpp_source.html b/BookOffers_8cpp_source.html index b28cb428e5..e26f44bf9a 100644 --- a/BookOffers_8cpp_source.html +++ b/BookOffers_8cpp_source.html @@ -305,7 +305,7 @@ $(function() {
@ nullValue
'null' value
Definition: json_value.h:37
static LimitRange constexpr bookOffers
Limits for the book_offers command.
-
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:185
+
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:186
Json::Value computeBookChanges(std::shared_ptr< L const > const &lpAccepted)
Definition: BookChanges.h:47
Json::Value invalid_field_error(std::string const &name)
Definition: ErrorCodes.h:315
Json::Value object_field_error(std::string const &name)
Definition: ErrorCodes.h:291
diff --git a/Book__test_8cpp_source.html b/Book__test_8cpp_source.html index 6722b81ac5..998cea8d4d 100644 --- a/Book__test_8cpp_source.html +++ b/Book__test_8cpp_source.html @@ -1844,12 +1844,12 @@ $(function() {
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
A transaction testing environment.
Definition: Env.h:120
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition: Env.cpp:111
-
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:532
+
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:534
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
Account const & master
Definition: Env.h:124
-
Application & app()
Definition: Env.h:258
-
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:767
+
Application & app()
Definition: Env.h:260
+
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:769
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
Match the number of items in the account's owner directory.
Definition: owners.h:73
Check a set of conditions.
Definition: require.h:65
diff --git a/CTID_8h_source.html b/CTID_8h_source.html index 10bafff1e9..0a197b36af 100644 --- a/CTID_8h_source.html +++ b/CTID_8h_source.html @@ -108,67 +108,73 @@ $(function() {
30
31namespace RPC {
32
-
33inline std::optional<std::string>
-
34encodeCTID(
-
35 uint32_t ledger_seq,
-
36 uint16_t txn_index,
-
37 uint16_t network_id) noexcept
-
38{
-
39 if (ledger_seq > 0x0FFF'FFFF)
-
40 return {};
+
33// CTID stands for Concise Transaction ID.
+
34//
+
35// The CTID comes from XLS-15d: Concise Transaction Identifier #34
+
36//
+
37// https://github.com/XRPLF/XRPL-Standards/discussions/34
+
38//
+
39// The Concise Transaction ID provides a way to identify a transaction
+
40// that includes which network the transaction was submitted to.
41
-
42 uint64_t ctidValue =
-
43 ((0xC000'0000ULL + static_cast<uint64_t>(ledger_seq)) << 32) +
-
44 (static_cast<uint64_t>(txn_index) << 16) + network_id;
-
45
-
46 std::stringstream buffer;
-
47 buffer << std::hex << std::uppercase << std::setfill('0') << std::setw(16)
-
48 << ctidValue;
-
49 return {buffer.str()};
-
50}
+
42inline std::optional<std::string>
+
43encodeCTID(uint32_t ledgerSeq, uint32_t txnIndex, uint32_t networkID) noexcept
+
44{
+
45 if (ledgerSeq > 0x0FFF'FFFF || txnIndex > 0xFFFF || networkID > 0xFFFF)
+
46 return {};
+
47
+
48 uint64_t ctidValue =
+
49 ((0xC000'0000ULL + static_cast<uint64_t>(ledgerSeq)) << 32) +
+
50 (static_cast<uint64_t>(txnIndex) << 16) + networkID;
51
-
52template <typename T>
-
53inline std::optional<std::tuple<uint32_t, uint16_t, uint16_t>>
-
54decodeCTID(const T ctid) noexcept
-
55{
-
56 uint64_t ctidValue{0};
-
57 if constexpr (
-
58 std::is_same_v<T, std::string> || std::is_same_v<T, char*> ||
-
59 std::is_same_v<T, const char*> || std::is_same_v<T, std::string_view>)
-
60 {
-
61 std::string const ctidString(ctid);
-
62
-
63 if (ctidString.length() != 16)
-
64 return {};
-
65
-
66 if (!boost::regex_match(ctidString, boost::regex("^[0-9A-Fa-f]+$")))
-
67 return {};
+
52 std::stringstream buffer;
+
53 buffer << std::hex << std::uppercase << std::setfill('0') << std::setw(16)
+
54 << ctidValue;
+
55 return {buffer.str()};
+
56}
+
57
+
58template <typename T>
+
59inline std::optional<std::tuple<uint32_t, uint16_t, uint16_t>>
+
60decodeCTID(const T ctid) noexcept
+
61{
+
62 uint64_t ctidValue{0};
+
63 if constexpr (
+
64 std::is_same_v<T, std::string> || std::is_same_v<T, char*> ||
+
65 std::is_same_v<T, const char*> || std::is_same_v<T, std::string_view>)
+
66 {
+
67 std::string const ctidString(ctid);
68
-
69 ctidValue = std::stoull(ctidString, nullptr, 16);
-
70 }
-
71 else if constexpr (std::is_integral_v<T>)
-
72 ctidValue = ctid;
-
73 else
-
74 return {};
-
75
-
76 if ((ctidValue & 0xF000'0000'0000'0000ULL) != 0xC000'0000'0000'0000ULL)
-
77 return {};
-
78
-
79 uint32_t ledger_seq = (ctidValue >> 32) & 0xFFFF'FFFUL;
-
80 uint16_t txn_index = (ctidValue >> 16) & 0xFFFFU;
-
81 uint16_t network_id = ctidValue & 0xFFFFU;
-
82 return {{ledger_seq, txn_index, network_id}};
-
83}
+
69 if (ctidString.length() != 16)
+
70 return {};
+
71
+
72 if (!boost::regex_match(ctidString, boost::regex("^[0-9A-Fa-f]+$")))
+
73 return {};
+
74
+
75 ctidValue = std::stoull(ctidString, nullptr, 16);
+
76 }
+
77 else if constexpr (std::is_integral_v<T>)
+
78 ctidValue = ctid;
+
79 else
+
80 return {};
+
81
+
82 if ((ctidValue & 0xF000'0000'0000'0000ULL) != 0xC000'0000'0000'0000ULL)
+
83 return {};
84
-
85} // namespace RPC
-
86} // namespace ripple
-
87
-
88#endif
+
85 uint32_t ledger_seq = (ctidValue >> 32) & 0xFFFF'FFFUL;
+
86 uint16_t txn_index = (ctidValue >> 16) & 0xFFFFU;
+
87 uint16_t network_id = ctidValue & 0xFFFFU;
+
88 return {{ledger_seq, txn_index, network_id}};
+
89}
+
90
+
91} // namespace RPC
+
92} // namespace ripple
+
93
+
94#endif
T hex(T... args)
-
std::optional< std::tuple< uint32_t, uint16_t, uint16_t > > decodeCTID(const T ctid) noexcept
Definition: CTID.h:54
-
std::optional< std::string > encodeCTID(uint32_t ledger_seq, uint16_t txn_index, uint16_t network_id) noexcept
Definition: CTID.h:34
+
std::optional< std::tuple< uint32_t, uint16_t, uint16_t > > decodeCTID(const T ctid) noexcept
Definition: CTID.h:60
+
std::optional< std::string > encodeCTID(uint32_t ledgerSeq, uint32_t txnIndex, uint32_t networkID) noexcept
Definition: CTID.h:43
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
diff --git a/CanDelete_8cpp_source.html b/CanDelete_8cpp_source.html index 13b864e195..c9437a8e8f 100644 --- a/CanDelete_8cpp_source.html +++ b/CanDelete_8cpp_source.html @@ -196,7 +196,7 @@ $(function() {
T max(T... args)
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
-
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:185
+
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:186
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
@ rpcNOT_READY
Definition: ErrorCodes.h:60
@ rpcINVALID_PARAMS
Definition: ErrorCodes.h:84
diff --git a/CanonicalTXSet_8cpp_source.html b/CanonicalTXSet_8cpp_source.html index 36d78f6562..aed81c6028 100644 --- a/CanonicalTXSet_8cpp_source.html +++ b/CanonicalTXSet_8cpp_source.html @@ -152,8 +152,8 @@ $(function() {
74 std::shared_ptr<STTx const> result;
75 uint256 const effectiveAccount{accountKey(tx->getAccountID(sfAccount))};
76
-
77 Key const after(effectiveAccount, tx->getSeqProxy(), beast::zero);
-
78 auto const itrNext{map_.lower_bound(after)};
+
77 Key const after(effectiveAccount, tx->getSeqProxy(), beast::zero);
+
78 auto const itrNext{map_.lower_bound(after)};
79 if (itrNext != map_.end() &&
80 itrNext->first.getAccount() == effectiveAccount)
81 {
@@ -179,7 +179,7 @@ $(function() {
T make_pair(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
bool operator<(Slice const &lhs, Slice const &rhs) noexcept
Definition: Slice.h:223
-
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: Escrow.cpp:86
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: View.cpp:2127
diff --git a/Check__test_8cpp_source.html b/Check__test_8cpp_source.html index a3f17ae4fd..cf86fdab69 100644 --- a/Check__test_8cpp_source.html +++ b/Check__test_8cpp_source.html @@ -2853,9 +2853,9 @@ $(function() {
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:120
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:455
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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:767
+
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:769
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/ClosureCounter__test_8cpp_source.html b/ClosureCounter__test_8cpp_source.html index ececf76ca9..c666aaad01 100644 --- a/ClosureCounter__test_8cpp_source.html +++ b/ClosureCounter__test_8cpp_source.html @@ -451,7 +451,7 @@ $(function() {
void testWaitOnJoin()
void testArgs()
A transaction testing environment.
Definition: Env.h:120
-
Application & app()
Definition: Env.h:258
+
Application & app()
Definition: Env.h:260
@ kDisabled
Definition: Journal.h:42
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
diff --git a/Connect_8cpp_source.html b/Connect_8cpp_source.html index a2d4f13e3d..f788e690ad 100644 --- a/Connect_8cpp_source.html +++ b/Connect_8cpp_source.html @@ -160,7 +160,7 @@ $(function() {
bool standalone() const
Definition: Config.h:337
virtual void connect(beast::IP::Endpoint const &address)=0
Establish a peer connection to the specified endpoint.
@ intValue
signed integer value
Definition: json_value.h:38
-
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:185
+
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:186
Json::Value makeObjectValue(Value const &value, Json::StaticString const &field=jss::message)
Return a Json::objectValue with a single entry.
Definition: Handler.h:65
Json::Value missing_field_error(std::string const &name)
Definition: ErrorCodes.h:273
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
diff --git a/ConsensusTransSetSF_8cpp_source.html b/ConsensusTransSetSF_8cpp_source.html index a45bcdefc4..b763424371 100644 --- a/ConsensusTransSetSF_8cpp_source.html +++ b/ConsensusTransSetSF_8cpp_source.html @@ -207,7 +207,7 @@ $(function() {
bool retrieve(const key_type &key, T &data)
auto insert(key_type const &key, T const &value) -> std::enable_if_t<!IsKeyCache, ReturnType >
Insert the element into the container.
-
std::shared_ptr< Transaction > fetch_from_cache(uint256 const &)
+
std::shared_ptr< Transaction > fetch_from_cache(uint256 const &)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
diff --git a/Coroutine__test_8cpp_source.html b/Coroutine__test_8cpp_source.html index b7fa6f20de..00ddae7a89 100644 --- a/Coroutine__test_8cpp_source.html +++ b/Coroutine__test_8cpp_source.html @@ -285,7 +285,7 @@ $(function() {
void incorrect_order()
void run() override
Runs the suite.
A transaction testing environment.
Definition: Env.h:120
-
Application & app()
Definition: Env.h:258
+
Application & app()
Definition: Env.h:260
diff --git a/Counts_8h_source.html b/Counts_8h_source.html index e715fd9bdf..86623a2dce 100644 --- a/Counts_8h_source.html +++ b/Counts_8h_source.html @@ -160,9 +160,9 @@ $(function() {
87 std::size_t
88 attempts_needed() const
89 {
-
90 if (m_attempts >= Tuning::maxConnectAttempts)
+
90 if (m_attempts >= Tuning::maxConnectAttempts)
91 return 0;
-
92 return Tuning::maxConnectAttempts - m_attempts;
+
92 return Tuning::maxConnectAttempts - m_attempts;
93 }
94
96 std::size_t
@@ -430,7 +430,7 @@ $(function() {
@ connect
@ active
@ accept
-
@ maxConnectAttempts
+
@ maxConnectAttempts
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
T str(T... args)
diff --git a/Credentials__test_8cpp_source.html b/Credentials__test_8cpp_source.html index 5db2202137..c8bf3590f4 100644 --- a/Credentials__test_8cpp_source.html +++ b/Credentials__test_8cpp_source.html @@ -1214,10 +1214,10 @@ $(function() {
T data(T... args)
TER deleteSLE(ApplyView &view, std::shared_ptr< SLE > const &sleCredential, beast::Journal j)
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Definition: Indexes.cpp:536
-
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:32
-
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:50
-
Json::Value deleteCred(jtx::Account const &acc, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:66
-
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:83
+
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:32
+
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:50
+
Json::Value deleteCred(jtx::Account const &acc, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:66
+
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:83
std::uint32_t ownerCount(Env const &env, Account const &account)
Definition: TestHelpers.cpp:54
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
Definition: acctdelete.cpp:30
diff --git a/CrossingLimits__test_8cpp_source.html b/CrossingLimits__test_8cpp_source.html index 7b4bc2d3bc..1c94aa6aa4 100644 --- a/CrossingLimits__test_8cpp_source.html +++ b/CrossingLimits__test_8cpp_source.html @@ -660,7 +660,7 @@ $(function() {
void testOfferOverflow(FeatureBitset features)
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:120
-
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:532
+
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:534
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
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
diff --git a/DID__test_8cpp_source.html b/DID__test_8cpp_source.html index 4ee9af45a9..cd88929dbf 100644 --- a/DID__test_8cpp_source.html +++ b/DID__test_8cpp_source.html @@ -505,9 +505,9 @@ $(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
Keylet did(AccountID const &account) noexcept
Definition: Indexes.cpp:497
-
Json::Value del(jtx::Account const &account)
Definition: did.cpp:54
-
Json::Value set(jtx::Account const &account)
Definition: did.cpp:33
-
Json::Value setValid(jtx::Account const &account)
Definition: did.cpp:43
+
Json::Value del(jtx::Account const &account)
Definition: dids.cpp:54
+
Json::Value set(jtx::Account const &account)
Definition: dids.cpp:33
+
Json::Value setValid(jtx::Account const &account)
Definition: dids.cpp:43
std::uint32_t ownerCount(Env const &env, Account const &account)
Definition: TestHelpers.cpp:54
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
diff --git a/DNS__test_8cpp_source.html b/DNS__test_8cpp_source.html index fd4b2b1274..72ef5aed33 100644 --- a/DNS__test_8cpp_source.html +++ b/DNS__test_8cpp_source.html @@ -234,7 +234,7 @@ $(function() {
DNS_test()
Definition: DNS_test.cpp:46
std::condition_variable cv_
Definition: DNS_test.cpp:43
A transaction testing environment.
Definition: Env.h:120
-
Application & app()
Definition: Env.h:258
+
Application & app()
Definition: Env.h:260
beast::Journal const journal
Definition: Env.h:161
T clear(T... args)
diff --git a/Database_8cpp_source.html b/Database_8cpp_source.html index 7676beb365..c13d39847b 100644 --- a/Database_8cpp_source.html +++ b/Database_8cpp_source.html @@ -278,7 +278,7 @@ $(function() {
200Database::importInternal(Backend& dstBackend, Database& srcDB)
201{
202 Batch batch;
-
203 batch.reserve(batchWritePreallocationSize);
+
203 batch.reserve(batchWritePreallocationSize);
204 auto storeBatch = [&, fname = __func__]() {
205 try
206 {
@@ -306,7 +306,7 @@ $(function() {
228 return;
229
230 batch.emplace_back(std::move(nodeObject));
-
231 if (batch.size() >= batchWritePreallocationSize)
+
231 if (batch.size() >= batchWritePreallocationSize)
232 storeBatch();
233 });
234
@@ -422,8 +422,8 @@ $(function() {
void setCurrentThreadName(std::string_view newThreadName)
Changes the name of the caller thread.
FetchType
@ async
-
@ batchWritePreallocationSize
void read(nudb::detail::istream &is, std::size_t &u)
Definition: varint.h:121
+
@ batchWritePreallocationSize
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
static constexpr std::uint32_t XRP_LEDGER_EARLIEST_SEQ
The XRP ledger network's earliest allowed sequence.
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
Definition: BasicConfig.h:355
diff --git a/Database__test_8cpp_source.html b/Database__test_8cpp_source.html index 99d179c4d8..0bca7020ef 100644 --- a/Database__test_8cpp_source.html +++ b/Database__test_8cpp_source.html @@ -875,7 +875,7 @@ $(function() {
void set(std::string const &key, std::string const &value)
Set a key/value pair.
Definition: BasicConfig.cpp:41
Definition: SuiteJournal.h:101
A transaction testing environment.
Definition: Env.h:120
-
Application & app()
Definition: Env.h:258
+
Application & app()
Definition: Env.h:260
@ kWarning
Definition: Journal.h:38
Definition: Check_test.cpp:27
diff --git a/DeliverMin__test_8cpp_source.html b/DeliverMin__test_8cpp_source.html index 1ce50ed0e2..b3e503c1e1 100644 --- a/DeliverMin__test_8cpp_source.html +++ b/DeliverMin__test_8cpp_source.html @@ -238,8 +238,8 @@ $(function() {
void run() override
Runs the suite.
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:120
-
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:532
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:534
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
diff --git a/DeliveredAmount__test_8cpp_source.html b/DeliveredAmount__test_8cpp_source.html index 7553c5f0d0..6ea4321c77 100644 --- a/DeliveredAmount__test_8cpp_source.html +++ b/DeliveredAmount__test_8cpp_source.html @@ -445,11 +445,11 @@ $(function() {
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:120
-
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:532
+
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:534
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:258
-
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:767
+
Application & app()
Definition: Env.h:260
+
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:769
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 bd69494748..6051934f3b 100644 --- a/DepositAuth__test_8cpp_source.html +++ b/DepositAuth__test_8cpp_source.html @@ -906,7 +906,7 @@ $(function() {
828 Account const john{"john"};
829
830 {
-
831 testcase("Payment failed with disabled credentials rule.");
+
831 testcase("Payment failure with disabled credentials rule.");
832
833 Env env(*this, supported_amendments() - featureCredentials);
834
@@ -1008,7 +1008,7 @@ $(function() {
930 }
931
932 {
-
933 testcase("Payment failed with invalid credentials.");
+
933 testcase("Payment failure with invalid credentials.");
934
935 Env env(*this);
936
@@ -1284,7 +1284,7 @@ $(function() {
1206 Account const zelda{"zelda"};
1207
1208 {
-
1209 testcase("Payment failed with expired credentials.");
+
1209 testcase("Payment failure with expired credentials.");
1210
1211 Env env(*this);
1212
@@ -1431,7 +1431,7 @@ $(function() {
1353 {
1354 using namespace std::chrono;
1355
-
1356 testcase("Escrow failed with expired credentials.");
+
1356 testcase("Escrow failure with expired credentials.");
1357
1358 Env env(*this);
1359
@@ -1678,12 +1678,12 @@ $(function() {
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
A transaction testing environment.
Definition: Env.h:120
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:212
-
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:532
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:534
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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:281
-
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:767
+
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:283
+
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:769
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
@@ -1708,9 +1708,9 @@ $(function() {
@ arrayValue
array value (ordered list)
Definition: json_value.h:43
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
-
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:32
-
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:50
-
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:83
+
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:32
+
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:50
+
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:83
Json::Value unauth(Account const &account, Account const &unauth)
Remove preauthorization for deposit.
Definition: deposit.cpp:43
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition: deposit.cpp:32
Json::Value unauthCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition: deposit.cpp:74
diff --git a/DepositAuthorized_8cpp_source.html b/DepositAuthorized_8cpp_source.html index 54804c7c33..7c8bbc98d0 100644 --- a/DepositAuthorized_8cpp_source.html +++ b/DepositAuthorized_8cpp_source.html @@ -286,7 +286,7 @@ $(function() {
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition: base_uint.h:503
T emplace(T... args)
-
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:185
+
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:186
void inject_error(error_code_i code, JsonValue &json)
Add or update the json update to reflect the error code.
Definition: ErrorCodes.h:223
std::string expected_field_message(std::string const &name, std::string const &type)
Definition: ErrorCodes.h:327
Status lookupLedger(std::shared_ptr< ReadView const > &ledger, JsonContext &context, Json::Value &result)
Look up a ledger from a request and fill a Json::Result with the data representing a ledger.
Definition: RPCHelpers.cpp:622
diff --git a/DepositAuthorized__test_8cpp_source.html b/DepositAuthorized__test_8cpp_source.html index b74a95b914..c0eddd37ed 100644 --- a/DepositAuthorized__test_8cpp_source.html +++ b/DepositAuthorized__test_8cpp_source.html @@ -143,579 +143,583 @@ $(function() {
65 void
66 testValid()
67 {
-
68 using namespace jtx;
-
69 Account const alice{"alice"};
-
70 Account const becky{"becky"};
-
71 Account const carol{"carol"};
-
72
-
73 Env env(*this);
-
74 env.fund(XRP(1000), alice, becky, carol);
-
75 env.close();
-
76
-
77 // becky is authorized to deposit to herself.
-
78 validateDepositAuthResult(
-
79 env.rpc(
-
80 "json",
-
81 "deposit_authorized",
-
82 depositAuthArgs(becky, becky, "validated").toStyledString()),
-
83 true);
-
84
-
85 // alice should currently be authorized to deposit to becky.
-
86 validateDepositAuthResult(
-
87 env.rpc(
-
88 "json",
-
89 "deposit_authorized",
-
90 depositAuthArgs(alice, becky, "validated").toStyledString()),
-
91 true);
-
92
-
93 // becky sets the DepositAuth flag in the current ledger.
-
94 env(fset(becky, asfDepositAuth));
-
95
-
96 // alice is no longer authorized to deposit to becky in current ledger.
-
97 validateDepositAuthResult(
-
98 env.rpc(
-
99 "json",
-
100 "deposit_authorized",
-
101 depositAuthArgs(alice, becky).toStyledString()),
-
102 false);
-
103 env.close();
-
104
-
105 // becky is still authorized to deposit to herself.
-
106 validateDepositAuthResult(
-
107 env.rpc(
-
108 "json",
-
109 "deposit_authorized",
-
110 depositAuthArgs(becky, becky, "validated").toStyledString()),
-
111 true);
-
112
-
113 // It's not a reciprocal arrangement. becky can deposit to alice.
-
114 validateDepositAuthResult(
-
115 env.rpc(
-
116 "json",
-
117 "deposit_authorized",
-
118 depositAuthArgs(becky, alice, "current").toStyledString()),
-
119 true);
-
120
-
121 // becky creates a deposit authorization for alice.
-
122 env(deposit::auth(becky, alice));
-
123 env.close();
-
124
-
125 // alice is now authorized to deposit to becky.
-
126 validateDepositAuthResult(
-
127 env.rpc(
-
128 "json",
-
129 "deposit_authorized",
-
130 depositAuthArgs(alice, becky, "closed").toStyledString()),
-
131 true);
-
132
-
133 // carol is still not authorized to deposit to becky.
-
134 validateDepositAuthResult(
-
135 env.rpc(
-
136 "json",
-
137 "deposit_authorized",
-
138 depositAuthArgs(carol, becky).toStyledString()),
-
139 false);
-
140
-
141 // becky clears the DepositAuth flag so carol becomes authorized.
-
142 env(fclear(becky, asfDepositAuth));
-
143 env.close();
-
144
-
145 validateDepositAuthResult(
-
146 env.rpc(
-
147 "json",
-
148 "deposit_authorized",
-
149 depositAuthArgs(carol, becky).toStyledString()),
-
150 true);
-
151
-
152 // alice is still authorized to deposit to becky.
-
153 validateDepositAuthResult(
-
154 env.rpc(
-
155 "json",
-
156 "deposit_authorized",
-
157 depositAuthArgs(alice, becky).toStyledString()),
-
158 true);
-
159 }
-
160
-
161 // Test malformed cases.
-
162 void
-
163 testErrors()
-
164 {
-
165 using namespace jtx;
-
166 Account const alice{"alice"};
-
167 Account const becky{"becky"};
-
168
-
169 // Lambda that checks the (error) result of deposit_authorized.
-
170 auto verifyErr = [this](
-
171 Json::Value const& result,
-
172 char const* error,
-
173 char const* errorMsg) {
-
174 BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
-
175 BEAST_EXPECT(result[jss::result][jss::error] == error);
-
176 BEAST_EXPECT(result[jss::result][jss::error_message] == errorMsg);
-
177 };
-
178
-
179 Env env(*this);
-
180 {
-
181 // Missing source_account field.
-
182 Json::Value args{depositAuthArgs(alice, becky)};
-
183 args.removeMember(jss::source_account);
-
184 Json::Value const result{
-
185 env.rpc("json", "deposit_authorized", args.toStyledString())};
-
186 verifyErr(
-
187 result, "invalidParams", "Missing field 'source_account'.");
-
188 }
-
189 {
-
190 // Non-string source_account field.
-
191 Json::Value args{depositAuthArgs(alice, becky)};
-
192 args[jss::source_account] = 7.3;
-
193 Json::Value const result{
-
194 env.rpc("json", "deposit_authorized", args.toStyledString())};
-
195 verifyErr(
-
196 result,
-
197 "invalidParams",
-
198 "Invalid field 'source_account', not a string.");
-
199 }
-
200 {
-
201 // Corrupt source_account field.
-
202 Json::Value args{depositAuthArgs(alice, becky)};
-
203 args[jss::source_account] = "rG1QQv2nh2gr7RCZ!P8YYcBUKCCN633jCn";
-
204 Json::Value const result{
-
205 env.rpc("json", "deposit_authorized", args.toStyledString())};
-
206 verifyErr(result, "actMalformed", "Account malformed.");
-
207 }
-
208 {
-
209 // Missing destination_account field.
-
210 Json::Value args{depositAuthArgs(alice, becky)};
-
211 args.removeMember(jss::destination_account);
-
212 Json::Value const result{
-
213 env.rpc("json", "deposit_authorized", args.toStyledString())};
-
214 verifyErr(
-
215 result,
-
216 "invalidParams",
-
217 "Missing field 'destination_account'.");
-
218 }
-
219 {
-
220 // Non-string destination_account field.
-
221 Json::Value args{depositAuthArgs(alice, becky)};
-
222 args[jss::destination_account] = 7.3;
-
223 Json::Value const result{
-
224 env.rpc("json", "deposit_authorized", args.toStyledString())};
-
225 verifyErr(
-
226 result,
-
227 "invalidParams",
-
228 "Invalid field 'destination_account', not a string.");
-
229 }
-
230 {
-
231 // Corrupt destination_account field.
-
232 Json::Value args{depositAuthArgs(alice, becky)};
-
233 args[jss::destination_account] =
-
234 "rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas";
-
235 Json::Value const result{
-
236 env.rpc("json", "deposit_authorized", args.toStyledString())};
-
237 verifyErr(result, "actMalformed", "Account malformed.");
-
238 }
-
239 {
-
240 // Request an invalid ledger.
-
241 Json::Value args{depositAuthArgs(alice, becky, "-1")};
-
242 Json::Value const result{
-
243 env.rpc("json", "deposit_authorized", args.toStyledString())};
-
244 verifyErr(result, "invalidParams", "ledgerIndexMalformed");
-
245 }
-
246 {
-
247 // Request a ledger that doesn't exist yet as a string.
-
248 Json::Value args{depositAuthArgs(alice, becky, "17")};
-
249 Json::Value const result{
-
250 env.rpc("json", "deposit_authorized", args.toStyledString())};
-
251 verifyErr(result, "lgrNotFound", "ledgerNotFound");
-
252 }
-
253 {
-
254 // Request a ledger that doesn't exist yet.
-
255 Json::Value args{depositAuthArgs(alice, becky)};
-
256 args[jss::ledger_index] = 17;
-
257 Json::Value const result{
-
258 env.rpc("json", "deposit_authorized", args.toStyledString())};
-
259 verifyErr(result, "lgrNotFound", "ledgerNotFound");
-
260 }
-
261 {
-
262 // alice is not yet funded.
-
263 Json::Value args{depositAuthArgs(alice, becky)};
-
264 Json::Value const result{
-
265 env.rpc("json", "deposit_authorized", args.toStyledString())};
-
266 verifyErr(result, "srcActNotFound", "Source account not found.");
-
267 }
-
268 env.fund(XRP(1000), alice);
-
269 env.close();
-
270 {
-
271 // becky is not yet funded.
-
272 Json::Value args{depositAuthArgs(alice, becky)};
-
273 Json::Value const result{
-
274 env.rpc("json", "deposit_authorized", args.toStyledString())};
-
275 verifyErr(
-
276 result, "dstActNotFound", "Destination account not found.");
-
277 }
-
278 env.fund(XRP(1000), becky);
-
279 env.close();
-
280 {
-
281 // Once becky is funded try it again and see it succeed.
-
282 Json::Value args{depositAuthArgs(alice, becky)};
-
283 Json::Value const result{
-
284 env.rpc("json", "deposit_authorized", args.toStyledString())};
-
285 validateDepositAuthResult(result, true);
-
286 }
-
287 }
-
288
-
289 void
-
290 checkCredentialsResponse(
-
291 Json::Value const& result,
-
292 jtx::Account const& src,
-
293 jtx::Account const& dst,
-
294 bool authorized,
-
295 std::vector<std::string> credentialIDs = {},
-
296 std::string_view error = "")
-
297 {
-
298 BEAST_EXPECT(
-
299 result[jss::status] == authorized ? jss::success : jss::error);
-
300 if (result.isMember(jss::deposit_authorized))
-
301 BEAST_EXPECT(result[jss::deposit_authorized] == authorized);
-
302 if (authorized)
-
303 BEAST_EXPECT(
-
304 result.isMember(jss::deposit_authorized) &&
-
305 (result[jss::deposit_authorized] == true));
-
306
-
307 BEAST_EXPECT(result.isMember(jss::error) == !error.empty());
-
308 if (!error.empty())
-
309 BEAST_EXPECT(result[jss::error].asString() == error);
-
310
-
311 if (authorized)
-
312 {
-
313 BEAST_EXPECT(result[jss::source_account] == src.human());
-
314 BEAST_EXPECT(result[jss::destination_account] == dst.human());
-
315
-
316 for (unsigned i = 0; i < credentialIDs.size(); ++i)
-
317 BEAST_EXPECT(result[jss::credentials][i] == credentialIDs[i]);
-
318 }
-
319 else
-
320 {
-
321 BEAST_EXPECT(result[jss::request].isObject());
-
322
-
323 auto const& request = result[jss::request];
-
324 BEAST_EXPECT(request[jss::command] == jss::deposit_authorized);
-
325 BEAST_EXPECT(request[jss::source_account] == src.human());
-
326 BEAST_EXPECT(request[jss::destination_account] == dst.human());
-
327
-
328 for (unsigned i = 0; i < credentialIDs.size(); ++i)
-
329 BEAST_EXPECT(request[jss::credentials][i] == credentialIDs[i]);
-
330 }
-
331 }
-
332
-
333 void
-
334 testCredentials()
-
335 {
-
336 using namespace jtx;
-
337
-
338 const char credType[] = "abcde";
+
68 testcase("Valid");
+
69 using namespace jtx;
+
70 Account const alice{"alice"};
+
71 Account const becky{"becky"};
+
72 Account const carol{"carol"};
+
73
+
74 Env env(*this);
+
75 env.fund(XRP(1000), alice, becky, carol);
+
76 env.close();
+
77
+
78 // becky is authorized to deposit to herself.
+
79 validateDepositAuthResult(
+
80 env.rpc(
+
81 "json",
+
82 "deposit_authorized",
+
83 depositAuthArgs(becky, becky, "validated").toStyledString()),
+
84 true);
+
85
+
86 // alice should currently be authorized to deposit to becky.
+
87 validateDepositAuthResult(
+
88 env.rpc(
+
89 "json",
+
90 "deposit_authorized",
+
91 depositAuthArgs(alice, becky, "validated").toStyledString()),
+
92 true);
+
93
+
94 // becky sets the DepositAuth flag in the current ledger.
+
95 env(fset(becky, asfDepositAuth));
+
96
+
97 // alice is no longer authorized to deposit to becky in current ledger.
+
98 validateDepositAuthResult(
+
99 env.rpc(
+
100 "json",
+
101 "deposit_authorized",
+
102 depositAuthArgs(alice, becky).toStyledString()),
+
103 false);
+
104 env.close();
+
105
+
106 // becky is still authorized to deposit to herself.
+
107 validateDepositAuthResult(
+
108 env.rpc(
+
109 "json",
+
110 "deposit_authorized",
+
111 depositAuthArgs(becky, becky, "validated").toStyledString()),
+
112 true);
+
113
+
114 // It's not a reciprocal arrangement. becky can deposit to alice.
+
115 validateDepositAuthResult(
+
116 env.rpc(
+
117 "json",
+
118 "deposit_authorized",
+
119 depositAuthArgs(becky, alice, "current").toStyledString()),
+
120 true);
+
121
+
122 // becky creates a deposit authorization for alice.
+
123 env(deposit::auth(becky, alice));
+
124 env.close();
+
125
+
126 // alice is now authorized to deposit to becky.
+
127 validateDepositAuthResult(
+
128 env.rpc(
+
129 "json",
+
130 "deposit_authorized",
+
131 depositAuthArgs(alice, becky, "closed").toStyledString()),
+
132 true);
+
133
+
134 // carol is still not authorized to deposit to becky.
+
135 validateDepositAuthResult(
+
136 env.rpc(
+
137 "json",
+
138 "deposit_authorized",
+
139 depositAuthArgs(carol, becky).toStyledString()),
+
140 false);
+
141
+
142 // becky clears the DepositAuth flag so carol becomes authorized.
+
143 env(fclear(becky, asfDepositAuth));
+
144 env.close();
+
145
+
146 validateDepositAuthResult(
+
147 env.rpc(
+
148 "json",
+
149 "deposit_authorized",
+
150 depositAuthArgs(carol, becky).toStyledString()),
+
151 true);
+
152
+
153 // alice is still authorized to deposit to becky.
+
154 validateDepositAuthResult(
+
155 env.rpc(
+
156 "json",
+
157 "deposit_authorized",
+
158 depositAuthArgs(alice, becky).toStyledString()),
+
159 true);
+
160 }
+
161
+
162 // Test malformed cases.
+
163 void
+
164 testErrors()
+
165 {
+
166 testcase("Errors");
+
167 using namespace jtx;
+
168 Account const alice{"alice"};
+
169 Account const becky{"becky"};
+
170
+
171 // Lambda that checks the (error) result of deposit_authorized.
+
172 auto verifyErr = [this](
+
173 Json::Value const& result,
+
174 char const* error,
+
175 char const* errorMsg) {
+
176 BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
+
177 BEAST_EXPECT(result[jss::result][jss::error] == error);
+
178 BEAST_EXPECT(result[jss::result][jss::error_message] == errorMsg);
+
179 };
+
180
+
181 Env env(*this);
+
182 {
+
183 // Missing source_account field.
+
184 Json::Value args{depositAuthArgs(alice, becky)};
+
185 args.removeMember(jss::source_account);
+
186 Json::Value const result{
+
187 env.rpc("json", "deposit_authorized", args.toStyledString())};
+
188 verifyErr(
+
189 result, "invalidParams", "Missing field 'source_account'.");
+
190 }
+
191 {
+
192 // Non-string source_account field.
+
193 Json::Value args{depositAuthArgs(alice, becky)};
+
194 args[jss::source_account] = 7.3;
+
195 Json::Value const result{
+
196 env.rpc("json", "deposit_authorized", args.toStyledString())};
+
197 verifyErr(
+
198 result,
+
199 "invalidParams",
+
200 "Invalid field 'source_account', not a string.");
+
201 }
+
202 {
+
203 // Corrupt source_account field.
+
204 Json::Value args{depositAuthArgs(alice, becky)};
+
205 args[jss::source_account] = "rG1QQv2nh2gr7RCZ!P8YYcBUKCCN633jCn";
+
206 Json::Value const result{
+
207 env.rpc("json", "deposit_authorized", args.toStyledString())};
+
208 verifyErr(result, "actMalformed", "Account malformed.");
+
209 }
+
210 {
+
211 // Missing destination_account field.
+
212 Json::Value args{depositAuthArgs(alice, becky)};
+
213 args.removeMember(jss::destination_account);
+
214 Json::Value const result{
+
215 env.rpc("json", "deposit_authorized", args.toStyledString())};
+
216 verifyErr(
+
217 result,
+
218 "invalidParams",
+
219 "Missing field 'destination_account'.");
+
220 }
+
221 {
+
222 // Non-string destination_account field.
+
223 Json::Value args{depositAuthArgs(alice, becky)};
+
224 args[jss::destination_account] = 7.3;
+
225 Json::Value const result{
+
226 env.rpc("json", "deposit_authorized", args.toStyledString())};
+
227 verifyErr(
+
228 result,
+
229 "invalidParams",
+
230 "Invalid field 'destination_account', not a string.");
+
231 }
+
232 {
+
233 // Corrupt destination_account field.
+
234 Json::Value args{depositAuthArgs(alice, becky)};
+
235 args[jss::destination_account] =
+
236 "rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas";
+
237 Json::Value const result{
+
238 env.rpc("json", "deposit_authorized", args.toStyledString())};
+
239 verifyErr(result, "actMalformed", "Account malformed.");
+
240 }
+
241 {
+
242 // Request an invalid ledger.
+
243 Json::Value args{depositAuthArgs(alice, becky, "-1")};
+
244 Json::Value const result{
+
245 env.rpc("json", "deposit_authorized", args.toStyledString())};
+
246 verifyErr(result, "invalidParams", "ledgerIndexMalformed");
+
247 }
+
248 {
+
249 // Request a ledger that doesn't exist yet as a string.
+
250 Json::Value args{depositAuthArgs(alice, becky, "17")};
+
251 Json::Value const result{
+
252 env.rpc("json", "deposit_authorized", args.toStyledString())};
+
253 verifyErr(result, "lgrNotFound", "ledgerNotFound");
+
254 }
+
255 {
+
256 // Request a ledger that doesn't exist yet.
+
257 Json::Value args{depositAuthArgs(alice, becky)};
+
258 args[jss::ledger_index] = 17;
+
259 Json::Value const result{
+
260 env.rpc("json", "deposit_authorized", args.toStyledString())};
+
261 verifyErr(result, "lgrNotFound", "ledgerNotFound");
+
262 }
+
263 {
+
264 // alice is not yet funded.
+
265 Json::Value args{depositAuthArgs(alice, becky)};
+
266 Json::Value const result{
+
267 env.rpc("json", "deposit_authorized", args.toStyledString())};
+
268 verifyErr(result, "srcActNotFound", "Source account not found.");
+
269 }
+
270 env.fund(XRP(1000), alice);
+
271 env.close();
+
272 {
+
273 // becky is not yet funded.
+
274 Json::Value args{depositAuthArgs(alice, becky)};
+
275 Json::Value const result{
+
276 env.rpc("json", "deposit_authorized", args.toStyledString())};
+
277 verifyErr(
+
278 result, "dstActNotFound", "Destination account not found.");
+
279 }
+
280 env.fund(XRP(1000), becky);
+
281 env.close();
+
282 {
+
283 // Once becky is funded try it again and see it succeed.
+
284 Json::Value args{depositAuthArgs(alice, becky)};
+
285 Json::Value const result{
+
286 env.rpc("json", "deposit_authorized", args.toStyledString())};
+
287 validateDepositAuthResult(result, true);
+
288 }
+
289 }
+
290
+
291 void
+
292 checkCredentialsResponse(
+
293 Json::Value const& result,
+
294 jtx::Account const& src,
+
295 jtx::Account const& dst,
+
296 bool authorized,
+
297 std::vector<std::string> credentialIDs = {},
+
298 std::string_view error = "")
+
299 {
+
300 BEAST_EXPECT(
+
301 result[jss::status] == authorized ? jss::success : jss::error);
+
302 if (result.isMember(jss::deposit_authorized))
+
303 BEAST_EXPECT(result[jss::deposit_authorized] == authorized);
+
304 if (authorized)
+
305 BEAST_EXPECT(
+
306 result.isMember(jss::deposit_authorized) &&
+
307 (result[jss::deposit_authorized] == true));
+
308
+
309 BEAST_EXPECT(result.isMember(jss::error) == !error.empty());
+
310 if (!error.empty())
+
311 BEAST_EXPECT(result[jss::error].asString() == error);
+
312
+
313 if (authorized)
+
314 {
+
315 BEAST_EXPECT(result[jss::source_account] == src.human());
+
316 BEAST_EXPECT(result[jss::destination_account] == dst.human());
+
317
+
318 for (unsigned i = 0; i < credentialIDs.size(); ++i)
+
319 BEAST_EXPECT(result[jss::credentials][i] == credentialIDs[i]);
+
320 }
+
321 else
+
322 {
+
323 BEAST_EXPECT(result[jss::request].isObject());
+
324
+
325 auto const& request = result[jss::request];
+
326 BEAST_EXPECT(request[jss::command] == jss::deposit_authorized);
+
327 BEAST_EXPECT(request[jss::source_account] == src.human());
+
328 BEAST_EXPECT(request[jss::destination_account] == dst.human());
+
329
+
330 for (unsigned i = 0; i < credentialIDs.size(); ++i)
+
331 BEAST_EXPECT(request[jss::credentials][i] == credentialIDs[i]);
+
332 }
+
333 }
+
334
+
335 void
+
336 testCredentials()
+
337 {
+
338 testcase("Credentials");
339
-
340 Account const alice{"alice"};
-
341 Account const becky{"becky"};
-
342 Account const diana{"diana"};
-
343 Account const carol{"carol"};
-
344
-
345 Env env(*this);
-
346 env.fund(XRP(1000), alice, becky, carol, diana);
-
347 env.close();
+
340 using namespace jtx;
+
341
+
342 const char credType[] = "abcde";
+
343
+
344 Account const alice{"alice"};
+
345 Account const becky{"becky"};
+
346 Account const diana{"diana"};
+
347 Account const carol{"carol"};
348
-
349 // carol recognize alice
-
350 env(credentials::create(alice, carol, credType));
+
349 Env env(*this);
+
350 env.fund(XRP(1000), alice, becky, carol, diana);
351 env.close();
-
352 // retrieve the index of the credentials
-
353 auto const jv = credentials::ledgerEntry(env, alice, carol, credType);
-
354 std::string const credIdx = jv[jss::result][jss::index].asString();
-
355
-
356 // becky sets the DepositAuth flag in the current ledger.
-
357 env(fset(becky, asfDepositAuth));
-
358 env.close();
+
352
+
353 // carol recognize alice
+
354 env(credentials::create(alice, carol, credType));
+
355 env.close();
+
356 // retrieve the index of the credentials
+
357 auto const jv = credentials::ledgerEntry(env, alice, carol, credType);
+
358 std::string const credIdx = jv[jss::result][jss::index].asString();
359
-
360 // becky authorize any account recognized by carol to make a payment
-
361 env(deposit::authCredentials(becky, {{carol, credType}}));
+
360 // becky sets the DepositAuth flag in the current ledger.
+
361 env(fset(becky, asfDepositAuth));
362 env.close();
363
-
364 {
-
365 testcase(
-
366 "deposit_authorized with credentials failed: empty array.");
+
364 // becky authorize any account recognized by carol to make a payment
+
365 env(deposit::authCredentials(becky, {{carol, credType}}));
+
366 env.close();
367
-
368 auto args = depositAuthArgs(alice, becky, "validated");
-
369 args[jss::credentials] = Json::arrayValue;
-
370
-
371 auto const jv =
-
372 env.rpc("json", "deposit_authorized", args.toStyledString());
-
373 checkCredentialsResponse(
-
374 jv[jss::result], alice, becky, false, {}, "invalidParams");
-
375 }
-
376
-
377 {
-
378 testcase(
-
379 "deposit_authorized with credentials failed: not a string "
-
380 "credentials");
-
381
-
382 auto args = depositAuthArgs(alice, becky, "validated");
-
383 args[jss::credentials] = Json::arrayValue;
-
384 args[jss::credentials].append(1);
-
385 args[jss::credentials].append(3);
-
386
-
387 auto const jv =
-
388 env.rpc("json", "deposit_authorized", args.toStyledString());
-
389 checkCredentialsResponse(
-
390 jv[jss::result], alice, becky, false, {}, "invalidParams");
-
391 }
-
392
-
393 {
-
394 testcase(
-
395 "deposit_authorized with credentials failed: not a hex string "
-
396 "credentials");
-
397
-
398 auto args = depositAuthArgs(alice, becky, "validated");
-
399 args[jss::credentials] = Json::arrayValue;
-
400 args[jss::credentials].append("hello world");
+
368 {
+
369 testcase(
+
370 "deposit_authorized with credentials failure: empty array.");
+
371
+
372 auto args = depositAuthArgs(alice, becky, "validated");
+
373 args[jss::credentials] = Json::arrayValue;
+
374
+
375 auto const jv =
+
376 env.rpc("json", "deposit_authorized", args.toStyledString());
+
377 checkCredentialsResponse(
+
378 jv[jss::result], alice, becky, false, {}, "invalidParams");
+
379 }
+
380
+
381 {
+
382 testcase(
+
383 "deposit_authorized with credentials failure: not a string "
+
384 "credentials");
+
385
+
386 auto args = depositAuthArgs(alice, becky, "validated");
+
387 args[jss::credentials] = Json::arrayValue;
+
388 args[jss::credentials].append(1);
+
389 args[jss::credentials].append(3);
+
390
+
391 auto const jv =
+
392 env.rpc("json", "deposit_authorized", args.toStyledString());
+
393 checkCredentialsResponse(
+
394 jv[jss::result], alice, becky, false, {}, "invalidParams");
+
395 }
+
396
+
397 {
+
398 testcase(
+
399 "deposit_authorized with credentials failure: not a hex string "
+
400 "credentials");
401
-
402 auto const jv =
-
403 env.rpc("json", "deposit_authorized", args.toStyledString());
-
404 checkCredentialsResponse(
-
405 jv[jss::result],
-
406 alice,
-
407 becky,
-
408 false,
-
409 {"hello world"},
-
410 "invalidParams");
-
411 }
-
412
-
413 {
-
414 testcase(
-
415 "deposit_authorized with credentials failed: not a credential "
-
416 "index");
-
417
-
418 auto args = depositAuthArgs(
-
419 alice,
-
420 becky,
-
421 "validated",
-
422 {"0127AB8B4B29CCDBB61AA51C0799A8A6BB80B86A9899807C11ED576AF8516"
-
423 "473"});
-
424
-
425 auto const jv =
-
426 env.rpc("json", "deposit_authorized", args.toStyledString());
-
427 checkCredentialsResponse(
-
428 jv[jss::result],
-
429 alice,
-
430 becky,
-
431 false,
-
432 {"0127AB8B4B29CCDBB61AA51C0799A8A6BB80B86A9899807C11ED576AF8516"
-
433 "473"},
-
434 "badCredentials");
-
435 }
-
436
-
437 {
-
438 testcase(
-
439 "deposit_authorized with credentials not authorized: "
-
440 "credential not accepted");
-
441 auto const jv = env.rpc(
-
442 "json",
-
443 "deposit_authorized",
-
444 depositAuthArgs(alice, becky, "validated", {credIdx})
-
445 .toStyledString());
-
446 checkCredentialsResponse(
-
447 jv[jss::result],
-
448 alice,
-
449 becky,
-
450 false,
-
451 {credIdx},
-
452 "badCredentials");
-
453 }
-
454
-
455 // alice accept credentials
-
456 env(credentials::accept(alice, carol, credType));
-
457 env.close();
+
402 auto args = depositAuthArgs(alice, becky, "validated");
+
403 args[jss::credentials] = Json::arrayValue;
+
404 args[jss::credentials].append("hello world");
+
405
+
406 auto const jv =
+
407 env.rpc("json", "deposit_authorized", args.toStyledString());
+
408 checkCredentialsResponse(
+
409 jv[jss::result],
+
410 alice,
+
411 becky,
+
412 false,
+
413 {"hello world"},
+
414 "invalidParams");
+
415 }
+
416
+
417 {
+
418 testcase(
+
419 "deposit_authorized with credentials failure: not a credential "
+
420 "index");
+
421
+
422 auto args = depositAuthArgs(
+
423 alice,
+
424 becky,
+
425 "validated",
+
426 {"0127AB8B4B29CCDBB61AA51C0799A8A6BB80B86A9899807C11ED576AF8516"
+
427 "473"});
+
428
+
429 auto const jv =
+
430 env.rpc("json", "deposit_authorized", args.toStyledString());
+
431 checkCredentialsResponse(
+
432 jv[jss::result],
+
433 alice,
+
434 becky,
+
435 false,
+
436 {"0127AB8B4B29CCDBB61AA51C0799A8A6BB80B86A9899807C11ED576AF8516"
+
437 "473"},
+
438 "badCredentials");
+
439 }
+
440
+
441 {
+
442 testcase(
+
443 "deposit_authorized with credentials not authorized: "
+
444 "credential not accepted");
+
445 auto const jv = env.rpc(
+
446 "json",
+
447 "deposit_authorized",
+
448 depositAuthArgs(alice, becky, "validated", {credIdx})
+
449 .toStyledString());
+
450 checkCredentialsResponse(
+
451 jv[jss::result],
+
452 alice,
+
453 becky,
+
454 false,
+
455 {credIdx},
+
456 "badCredentials");
+
457 }
458
-
459 {
-
460 testcase("deposit_authorized with duplicates in credentials");
-
461 auto const jv = env.rpc(
-
462 "json",
-
463 "deposit_authorized",
-
464 depositAuthArgs(alice, becky, "validated", {credIdx, credIdx})
-
465 .toStyledString());
-
466 checkCredentialsResponse(
-
467 jv[jss::result],
-
468 alice,
-
469 becky,
-
470 false,
-
471 {credIdx, credIdx},
-
472 "badCredentials");
-
473 }
-
474
-
475 {
-
476 static const std::vector<std::string> credIds = {
-
477 "18004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
-
478 "E4",
-
479 "28004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
-
480 "E4",
-
481 "38004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
+
459 // alice accept credentials
+
460 env(credentials::accept(alice, carol, credType));
+
461 env.close();
+
462
+
463 {
+
464 testcase("deposit_authorized with duplicates in credentials");
+
465 auto const jv = env.rpc(
+
466 "json",
+
467 "deposit_authorized",
+
468 depositAuthArgs(alice, becky, "validated", {credIdx, credIdx})
+
469 .toStyledString());
+
470 checkCredentialsResponse(
+
471 jv[jss::result],
+
472 alice,
+
473 becky,
+
474 false,
+
475 {credIdx, credIdx},
+
476 "badCredentials");
+
477 }
+
478
+
479 {
+
480 static const std::vector<std::string> credIds = {
+
481 "18004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
482 "E4",
-
483 "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
+
483 "28004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
484 "E4",
-
485 "58004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
+
485 "38004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
486 "E4",
-
487 "68004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
+
487 "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
488 "E4",
-
489 "78004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
+
489 "58004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
490 "E4",
-
491 "88004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
+
491 "68004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
492 "E4",
-
493 "98004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
-
494 "E4"};
-
495 assert(credIds.size() > maxCredentialsArraySize);
-
496
-
497 testcase("deposit_authorized too long credentials");
-
498 auto const jv = env.rpc(
-
499 "json",
-
500 "deposit_authorized",
-
501 depositAuthArgs(alice, becky, "validated", credIds)
-
502 .toStyledString());
-
503 checkCredentialsResponse(
-
504 jv[jss::result], alice, becky, false, credIds, "invalidParams");
-
505 }
-
506
-
507 {
-
508 testcase("deposit_authorized with credentials");
-
509 auto const jv = env.rpc(
-
510 "json",
-
511 "deposit_authorized",
-
512 depositAuthArgs(alice, becky, "validated", {credIdx})
-
513 .toStyledString());
-
514 checkCredentialsResponse(
-
515 jv[jss::result], alice, becky, true, {credIdx});
-
516 }
-
517
-
518 {
-
519 // diana recognize becky
-
520 env(credentials::create(becky, diana, credType));
-
521 env.close();
-
522 env(credentials::accept(becky, diana, credType));
-
523 env.close();
-
524
-
525 // retrieve the index of the credentials
-
526 auto jv = credentials::ledgerEntry(env, becky, diana, credType);
-
527 std::string const credBecky =
-
528 jv[jss::result][jss::index].asString();
-
529
-
530 testcase("deposit_authorized account without preauth");
-
531 jv = env.rpc(
-
532 "json",
-
533 "deposit_authorized",
-
534 depositAuthArgs(becky, alice, "validated", {credBecky})
-
535 .toStyledString());
-
536 checkCredentialsResponse(
-
537 jv[jss::result], becky, alice, true, {credBecky});
-
538 }
-
539
-
540 {
-
541 // carol recognize diana
-
542 env(credentials::create(diana, carol, credType));
-
543 env.close();
-
544 env(credentials::accept(diana, carol, credType));
-
545 env.close();
-
546 // retrieve the index of the credentials
-
547 auto jv = credentials::ledgerEntry(env, alice, carol, credType);
-
548 std::string const credDiana =
-
549 jv[jss::result][jss::index].asString();
-
550
-
551 // alice try to use credential for different account
-
552 jv = env.rpc(
-
553 "json",
-
554 "deposit_authorized",
-
555 depositAuthArgs(becky, alice, "validated", {credDiana})
-
556 .toStyledString());
-
557 checkCredentialsResponse(
-
558 jv[jss::result],
-
559 becky,
-
560 alice,
-
561 false,
-
562 {credDiana},
-
563 "badCredentials");
-
564 }
-
565
-
566 {
-
567 testcase("deposit_authorized with expired credentials");
-
568
-
569 // check expired credentials
-
570 const char credType2[] = "fghijk";
-
571 std::uint32_t const x = env.current()
-
572 ->info()
-
573 .parentCloseTime.time_since_epoch()
-
574 .count() +
-
575 40;
-
576
-
577 // create credentials with expire time 40s
-
578 auto jv = credentials::create(alice, carol, credType2);
-
579 jv[sfExpiration.jsonName] = x;
-
580 env(jv);
-
581 env.close();
-
582 env(credentials::accept(alice, carol, credType2));
-
583 env.close();
-
584 jv = credentials::ledgerEntry(env, alice, carol, credType2);
-
585 std::string const credIdx2 = jv[jss::result][jss::index].asString();
-
586
-
587 // becky sets the DepositAuth flag in the current ledger.
-
588 env(fset(becky, asfDepositAuth));
-
589 env.close();
+
493 "78004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
+
494 "E4",
+
495 "88004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
+
496 "E4",
+
497 "98004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
+
498 "E4"};
+
499 assert(credIds.size() > maxCredentialsArraySize);
+
500
+
501 testcase("deposit_authorized too long credentials");
+
502 auto const jv = env.rpc(
+
503 "json",
+
504 "deposit_authorized",
+
505 depositAuthArgs(alice, becky, "validated", credIds)
+
506 .toStyledString());
+
507 checkCredentialsResponse(
+
508 jv[jss::result], alice, becky, false, credIds, "invalidParams");
+
509 }
+
510
+
511 {
+
512 testcase("deposit_authorized with credentials");
+
513 auto const jv = env.rpc(
+
514 "json",
+
515 "deposit_authorized",
+
516 depositAuthArgs(alice, becky, "validated", {credIdx})
+
517 .toStyledString());
+
518 checkCredentialsResponse(
+
519 jv[jss::result], alice, becky, true, {credIdx});
+
520 }
+
521
+
522 {
+
523 // diana recognize becky
+
524 env(credentials::create(becky, diana, credType));
+
525 env.close();
+
526 env(credentials::accept(becky, diana, credType));
+
527 env.close();
+
528
+
529 // retrieve the index of the credentials
+
530 auto jv = credentials::ledgerEntry(env, becky, diana, credType);
+
531 std::string const credBecky =
+
532 jv[jss::result][jss::index].asString();
+
533
+
534 testcase("deposit_authorized account without preauth");
+
535 jv = env.rpc(
+
536 "json",
+
537 "deposit_authorized",
+
538 depositAuthArgs(becky, alice, "validated", {credBecky})
+
539 .toStyledString());
+
540 checkCredentialsResponse(
+
541 jv[jss::result], becky, alice, true, {credBecky});
+
542 }
+
543
+
544 {
+
545 // carol recognize diana
+
546 env(credentials::create(diana, carol, credType));
+
547 env.close();
+
548 env(credentials::accept(diana, carol, credType));
+
549 env.close();
+
550 // retrieve the index of the credentials
+
551 auto jv = credentials::ledgerEntry(env, alice, carol, credType);
+
552 std::string const credDiana =
+
553 jv[jss::result][jss::index].asString();
+
554
+
555 // alice try to use credential for different account
+
556 jv = env.rpc(
+
557 "json",
+
558 "deposit_authorized",
+
559 depositAuthArgs(becky, alice, "validated", {credDiana})
+
560 .toStyledString());
+
561 checkCredentialsResponse(
+
562 jv[jss::result],
+
563 becky,
+
564 alice,
+
565 false,
+
566 {credDiana},
+
567 "badCredentials");
+
568 }
+
569
+
570 {
+
571 testcase("deposit_authorized with expired credentials");
+
572
+
573 // check expired credentials
+
574 const char credType2[] = "fghijk";
+
575 std::uint32_t const x = env.current()
+
576 ->info()
+
577 .parentCloseTime.time_since_epoch()
+
578 .count() +
+
579 40;
+
580
+
581 // create credentials with expire time 40s
+
582 auto jv = credentials::create(alice, carol, credType2);
+
583 jv[sfExpiration.jsonName] = x;
+
584 env(jv);
+
585 env.close();
+
586 env(credentials::accept(alice, carol, credType2));
+
587 env.close();
+
588 jv = credentials::ledgerEntry(env, alice, carol, credType2);
+
589 std::string const credIdx2 = jv[jss::result][jss::index].asString();
590
-
591 // becky authorize any account recognized by carol to make a payment
-
592 env(deposit::authCredentials(becky, {{carol, credType2}}));
+
591 // becky sets the DepositAuth flag in the current ledger.
+
592 env(fset(becky, asfDepositAuth));
593 env.close();
594
-
595 {
-
596 // this should be fine
-
597 jv = env.rpc(
-
598 "json",
-
599 "deposit_authorized",
-
600 depositAuthArgs(alice, becky, "validated", {credIdx2})
-
601 .toStyledString());
-
602 checkCredentialsResponse(
-
603 jv[jss::result], alice, becky, true, {credIdx2});
-
604 }
-
605
-
606 // increase timer by 20s
-
607 env.close();
-
608 env.close();
-
609 {
-
610 // now credentials expired
-
611 jv = env.rpc(
-
612 "json",
-
613 "deposit_authorized",
-
614 depositAuthArgs(alice, becky, "validated", {credIdx2})
-
615 .toStyledString());
-
616
-
617 checkCredentialsResponse(
-
618 jv[jss::result],
-
619 alice,
-
620 becky,
-
621 false,
-
622 {credIdx2},
-
623 "badCredentials");
-
624 }
-
625 }
-
626 }
-
627
-
628 void
-
629 run() override
-
630 {
-
631 testValid();
-
632 testErrors();
-
633 testCredentials();
-
634 }
-
635};
-
636
-
637BEAST_DEFINE_TESTSUITE(DepositAuthorized, app, ripple);
-
638
-
639} // namespace test
-
640} // namespace ripple
+
595 // becky authorize any account recognized by carol to make a payment
+
596 env(deposit::authCredentials(becky, {{carol, credType2}}));
+
597 env.close();
+
598
+
599 {
+
600 // this should be fine
+
601 jv = env.rpc(
+
602 "json",
+
603 "deposit_authorized",
+
604 depositAuthArgs(alice, becky, "validated", {credIdx2})
+
605 .toStyledString());
+
606 checkCredentialsResponse(
+
607 jv[jss::result], alice, becky, true, {credIdx2});
+
608 }
+
609
+
610 // increase timer by 20s
+
611 env.close();
+
612 env.close();
+
613 {
+
614 // now credentials expired
+
615 jv = env.rpc(
+
616 "json",
+
617 "deposit_authorized",
+
618 depositAuthArgs(alice, becky, "validated", {credIdx2})
+
619 .toStyledString());
+
620
+
621 checkCredentialsResponse(
+
622 jv[jss::result],
+
623 alice,
+
624 becky,
+
625 false,
+
626 {credIdx2},
+
627 "badCredentials");
+
628 }
+
629 }
+
630 }
+
631
+
632 void
+
633 run() override
+
634 {
+
635 testValid();
+
636 testErrors();
+
637 testCredentials();
+
638 }
+
639};
+
640
+
641BEAST_DEFINE_TESTSUITE(DepositAuthorized, app, ripple);
+
642
+
643} // namespace test
+
644} // namespace ripple
Represents a JSON value.
Definition: json_value.h:148
@@ -725,26 +729,26 @@ $(function() {
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
void validateDepositAuthResult(Json::Value const &result, bool authorized)
-
void checkCredentialsResponse(Json::Value const &result, jtx::Account const &src, jtx::Account const &dst, bool authorized, std::vector< std::string > credentialIDs={}, std::string_view error="")
-
void testErrors()
-
void testCredentials()
-
void run() override
Runs the suite.
+
void checkCredentialsResponse(Json::Value const &result, jtx::Account const &src, jtx::Account const &dst, bool authorized, std::vector< std::string > credentialIDs={}, std::string_view error="")
+
void testErrors()
+
void testCredentials()
+
void run() override
Runs the suite.
void testValid()
static Json::Value depositAuthArgs(jtx::Account const &source, jtx::Account const &dest, std::string const &ledger="", std::vector< std::string > const &credentials={})
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:120
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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:767
+
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:769
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
@ arrayValue
array value (ordered list)
Definition: json_value.h:43
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
TER authorized(ApplyContext const &ctx, AccountID const &dst)
-
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:32
-
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:50
-
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:83
+
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:32
+
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:50
+
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:83
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition: deposit.cpp:32
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition: deposit.cpp:54
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition: flags.h:41
diff --git a/Directory__test_8cpp_source.html b/Directory__test_8cpp_source.html index 310a97ba57..f82b240352 100644 --- a/Directory__test_8cpp_source.html +++ b/Directory__test_8cpp_source.html @@ -603,7 +603,7 @@ $(function() {
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition: Env.cpp:111
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:212
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:455
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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
void enableFeature(uint256 const feature)
Definition: Env.cpp:588
diff --git a/rpc_2handlers_2Manifest_8cpp_source.html b/DoManifest_8cpp_source.html similarity index 95% rename from rpc_2handlers_2Manifest_8cpp_source.html rename to DoManifest_8cpp_source.html index 1b000e28b7..6408d03537 100644 --- a/rpc_2handlers_2Manifest_8cpp_source.html +++ b/DoManifest_8cpp_source.html @@ -5,7 +5,7 @@ -rippled: Manifest.cpp Source File +rippled: DoManifest.cpp Source File @@ -73,7 +73,7 @@ $(function() {
-
rpc/handlers/Manifest.cpp
+
DoManifest.cpp
1//------------------------------------------------------------------------------
@@ -105,7 +105,7 @@ $(function() {
27
28namespace ripple {
- +
31{
32 auto& params = context.params;
33
@@ -154,18 +154,18 @@ $(function() {
76} // namespace ripple
Represents a JSON value.
Definition: json_value.h:148
virtual ManifestCache & validatorManifests()=0
-
std::optional< std::uint32_t > getSequence(PublicKey const &pk) const
Returns master key's current manifest sequence.
-
std::optional< std::string > getManifest(PublicKey const &pk) const
Returns mainfest corresponding to a given public key.
-
std::optional< PublicKey > getSigningKey(PublicKey const &pk) const
Returns master key's current signing key.
-
PublicKey getMasterKey(PublicKey const &pk) const
Returns ephemeral signing key's master public key.
-
std::optional< std::string > getDomain(PublicKey const &pk) const
Returns domain claimed by a given public key.
+
std::optional< std::uint32_t > getSequence(PublicKey const &pk) const
Returns master key's current manifest sequence.
Definition: Manifest.cpp:335
+
std::optional< std::string > getManifest(PublicKey const &pk) const
Returns mainfest corresponding to a given public key.
Definition: Manifest.cpp:359
+
std::optional< PublicKey > getSigningKey(PublicKey const &pk) const
Returns master key's current signing key.
Definition: Manifest.cpp:311
+
PublicKey getMasterKey(PublicKey const &pk) const
Returns ephemeral signing key's master public key.
Definition: Manifest.cpp:323
+
std::optional< std::string > getDomain(PublicKey const &pk) const
Returns domain claimed by a given public key.
Definition: Manifest.cpp:347
void inject_error(error_code_i code, JsonValue &json)
Add or update the json update to reflect the error code.
Definition: ErrorCodes.h:223
Json::Value missing_field_error(std::string const &name)
Definition: ErrorCodes.h:273
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:114
@ rpcINVALID_PARAMS
Definition: ErrorCodes.h:84
-
Json::Value doManifest(RPC::JsonContext &)
+
Json::Value doManifest(RPC::JsonContext &context)
Definition: DoManifest.cpp:30
std::string base64_encode(std::uint8_t const *data, std::size_t len)
Definition: base64.cpp:239
@ manifest
Manifest.
Application & app
Definition: Context.h:41
diff --git a/Env_8cpp_source.html b/Env_8cpp_source.html index 7a02bcc206..1d27f02abf 100644 --- a/Env_8cpp_source.html +++ b/Env_8cpp_source.html @@ -726,37 +726,37 @@ $(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:596
-
bool parseFailureExpected_
Definition: Env.h:719
+
bool parseFailureExpected_
Definition: Env.h:721
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:762
- +
std::unordered_map< AccountID, Account > map_
Definition: Env.h:764
+
beast::unit_test::suite & test
Definition: Env.h:122
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:455
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
void postconditions(JTx const &jt, ParsedResult const &parsed, Json::Value const &jr=Json::Value())
Check expected postconditions of JTx submission.
Definition: Env.cpp:387
- +
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:484
-
AbstractClient & client()
Returns the connected client.
Definition: Env.h:288
+
AbstractClient & client()
Returns the connected client.
Definition: Env.h:290
void autofill_sig(JTx &jt)
Definition: Env.cpp:461
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:565
- +
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:513
void enableFeature(uint256 const feature)
Definition: Env.cpp:588
Account const & master
Definition: Env.h:124
Account const & lookup(AccountID const &id) const
Returns the Account given the AccountID.
Definition: Env.cpp:158
-
unsigned retries_
Definition: Env.h:720
-
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:493
+
unsigned retries_
Definition: Env.h:722
+
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:495
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:539
-
Application & app()
Definition: Env.h:258
+
Application & app()
Definition: Env.h:260
beast::Journal const journal
Definition: Env.h:161
-
ManualTimeKeeper & timeKeeper()
Definition: Env.h:270
+
ManualTimeKeeper & timeKeeper()
Definition: Env.h:272
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:567
-
bool close()
Close and advance the ledger.
Definition: Env.h:390
+
Env & apply(JsonValue &&jv, FN const &... fN)
Apply funclets and submit.
Definition: Env.h:569
+
bool close()
Close and advance the ledger.
Definition: Env.h:392
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
@@ -780,7 +780,7 @@ $(function() {
A namespace for easy access to logging severity values.
Definition: Journal.h:30
Severity
Severity level / threshold of a Journal message.
Definition: Journal.h:32
-
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:177
+
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:178
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition: Indexes.cpp:235
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:175
void fill_seq(Json::Value &jv, ReadView const &view)
Set the sequence number automatically.
Definition: utility.cpp:66
diff --git a/Env_8h_source.html b/Env_8h_source.html index 73c574f343..8f5fd60eb4 100644 --- a/Env_8h_source.html +++ b/Env_8h_source.html @@ -258,400 +258,402 @@ $(function() {
197 });
198 }
199
- -
214 : Env(suite_, envconfig(), features)
-
215 {
-
216 }
-
217
- - -
232 std::unique_ptr<Logs> logs = nullptr,
- -
234 : Env(suite_,
-
235 std::move(config),
- -
237 std::move(logs),
-
238 thresh)
-
239 {
-
240 }
-
241
- -
252 {
-
253 }
-
254
-
255 virtual ~Env() = default;
+ +
214 FeatureBitset features,
+
215 std::unique_ptr<Logs> logs = nullptr)
+
216 : Env(suite_, envconfig(), features, std::move(logs))
+
217 {
+
218 }
+
219
+ + +
234 std::unique_ptr<Logs> logs = nullptr,
+ +
236 : Env(suite_,
+
237 std::move(config),
+ +
239 std::move(logs),
+
240 thresh)
+
241 {
+
242 }
+
243
+ +
254 {
+
255 }
256
- - -
259 {
-
260 return *bundle_.app;
-
261 }
-
262
-
263 Application const&
-
264 app() const
-
265 {
-
266 return *bundle_.app;
-
267 }
-
268
- - -
271 {
-
272 return *bundle_.timeKeeper;
-
273 }
-
274
- - -
282 {
-
283 return timeKeeper().now();
-
284 }
-
285
- - -
289 {
-
290 return *bundle_.client;
-
291 }
-
292
-
298 template <class... Args>
- -
300 rpc(unsigned apiVersion,
- -
302 std::string const& cmd,
-
303 Args&&... args);
-
304
-
305 template <class... Args>
- -
307 rpc(unsigned apiVersion, std::string const& cmd, Args&&... args);
-
308
-
309 template <class... Args>
- - -
312 std::string const& cmd,
-
313 Args&&... args);
-
314
-
315 template <class... Args>
- -
317 rpc(std::string const& cmd, Args&&... args);
-
318
- -
328 current() const
-
329 {
-
330 return app().openLedger().current();
-
331 }
-
332
- -
341 closed();
-
342
-
362 bool
-
363 close(
-
364 NetClock::time_point closeTime,
-
365 std::optional<std::chrono::milliseconds> consensusDelay = std::nullopt);
-
366
-
374 template <class Rep, class Period>
-
375 bool
- -
377 {
-
378 // VFALCO Is this the correct time?
-
379 return close(now() + elapsed);
-
380 }
-
381
-
389 bool
- -
391 {
-
392 // VFALCO Is this the correct time?
-
393 return close(std::chrono::seconds(5));
-
394 }
-
395
-
399 void
-
400 trace(int howMany = -1)
-
401 {
-
402 trace_ = howMany;
-
403 }
-
404
-
406 void
- -
408 {
-
409 trace_ = 0;
-
410 }
-
411
-
412 void
- -
414 {
- -
416 }
-
417
-
419 void
- -
421 {
-
422 app().checkSigs(false);
-
423 }
-
424
-
425 // set rpc retries
-
426 void
-
427 set_retries(unsigned r = 5)
-
428 {
-
429 retries_ = r;
-
430 }
-
431
-
432 // get rpc retries
-
433 unsigned
-
434 retries() const
-
435 {
-
436 return retries_;
-
437 }
-
438
-
440 void
-
441 memoize(Account const& account);
-
442
-
445 Account const&
-
446 lookup(AccountID const& id) const;
-
447
-
448 Account const&
-
449 lookup(std::string const& base58ID) const;
- -
456 balance(Account const& account) const;
-
457
- -
463 seq(Account const& account) const;
-
464
-
468 // VFALCO NOTE This should return a unit-less amount
- -
470 balance(Account const& account, Issue const& issue) const;
-
471
- -
476 ownerCount(Account const& account) const;
-
477
- -
482 le(Account const& account) const;
-
483
- -
488 le(Keylet const& k) const;
-
489
-
491 template <class JsonValue, class... FN>
-
492 JTx
-
493 jt(JsonValue&& jv, FN const&... fN)
-
494 {
-
495 JTx jt(std::forward<JsonValue>(jv));
-
496 invoke(jt, fN...);
-
497 autofill(jt);
-
498 jt.stx = st(jt);
-
499 return jt;
-
500 }
-
501
-
503 template <class JsonValue, class... FN>
-
504 JTx
-
505 jtnofill(JsonValue&& jv, FN const&... fN)
-
506 {
-
507 JTx jt(std::forward<JsonValue>(jv));
-
508 invoke(jt, fN...);
- -
510 jt.stx = st(jt);
-
511 return jt;
-
512 }
-
513
-
517 template <class JsonValue, class... FN>
- -
519 json(JsonValue&& jv, FN const&... fN)
-
520 {
-
521 auto tj = jt(std::forward<JsonValue>(jv), fN...);
-
522 return std::move(tj.jv);
-
523 }
-
524
-
530 template <class... Args>
-
531 void
-
532 require(Args const&... args)
-
533 {
-
534 jtx::required(args...)(*this);
-
535 }
-
536
-
539 static ParsedResult
-
540 parseResult(Json::Value const& jr);
-
541
-
545 virtual void
-
546 submit(JTx const& jt);
-
547
-
551 void
- -
553
-
557 void
- -
559 JTx const& jt,
-
560 ParsedResult const& parsed,
-
561 Json::Value const& jr = Json::Value());
-
562
-
565 template <class JsonValue, class... FN>
-
566 Env&
-
567 apply(JsonValue&& jv, FN const&... fN)
-
568 {
-
569 submit(jt(std::forward<JsonValue>(jv), fN...));
-
570 return *this;
-
571 }
-
572
-
573 template <class JsonValue, class... FN>
-
574 Env&
-
575 operator()(JsonValue&& jv, FN const&... fN)
-
576 {
-
577 return apply(std::forward<JsonValue>(jv), fN...);
-
578 }
-
582 TER
-
583 ter() const
-
584 {
-
585 return ter_;
-
586 }
-
587
- -
597 meta();
-
598
- -
611 tx() const;
-
612
-
613 void
-
614 enableFeature(uint256 const feature);
-
615
-
616 void
-
617 disableFeature(uint256 const feature);
-
618
-
619private:
-
620 void
-
621 fund(bool setDefaultRipple, STAmount const& amount, Account const& account);
-
622
-
623 void
-
624 fund_arg(STAmount const& amount, Account const& account)
-
625 {
-
626 fund(true, amount, account);
-
627 }
-
628
-
629 template <std::size_t N>
-
630 void
-
631 fund_arg(STAmount const& amount, std::array<Account, N> const& list)
-
632 {
-
633 for (auto const& account : list)
-
634 fund(false, amount, account);
-
635 }
-
636
-
637public:
-
664 template <class Arg, class... Args>
-
665 void
-
666 fund(STAmount const& amount, Arg const& arg, Args const&... args)
-
667 {
-
668 fund_arg(amount, arg);
-
669 if constexpr (sizeof...(args) > 0)
-
670 fund(amount, args...);
-
671 }
-
672
-
691 void
-
692 trust(STAmount const& amount, Account const& account);
-
693
-
694 template <class... Accounts>
-
695 void
- -
697 STAmount const& amount,
-
698 Account const& to0,
-
699 Account const& to1,
-
700 Accounts const&... toN)
-
701 {
-
702 trust(amount, to0);
-
703 trust(amount, to1, toN...);
-
704 }
- -
712 ust(JTx const& jt);
-
713
-
714protected:
-
715 int trace_ = 0;
- - - - -
720 unsigned retries_ = 5;
-
721
- -
723 do_rpc(
-
724 unsigned apiVersion,
-
725 std::vector<std::string> const& args,
- -
727
-
728 void
- -
730
-
731 virtual void
-
732 autofill(JTx& jt);
-
733
- -
742 st(JTx const& jt);
-
743
-
744 // Invoke funclets on stx
-
745 // Note: The STTx may not be modified
-
746 template <class... FN>
-
747 void
-
748 invoke(STTx& stx, FN const&... fN)
-
749 {
-
750 (fN(*this, stx), ...);
-
751 }
-
752
-
753 // Invoke funclets on jt
-
754 template <class... FN>
-
755 void
-
756 invoke(JTx& jt, FN const&... fN)
-
757 {
-
758 (fN(*this, jt), ...);
-
759 }
-
760
-
761 // Map of account IDs to Account
- -
763};
-
764
-
765template <class... Args>
- - -
768 unsigned apiVersion,
- -
770 std::string const& cmd,
-
771 Args&&... args)
-
772{
-
773 return do_rpc(
-
774 apiVersion,
-
775 std::vector<std::string>{cmd, std::forward<Args>(args)...},
-
776 headers);
-
777}
-
778
-
779template <class... Args>
- -
781Env::rpc(unsigned apiVersion, std::string const& cmd, Args&&... args)
-
782{
-
783 return rpc(
-
784 apiVersion,
- -
786 cmd,
-
787 std::forward<Args>(args)...);
-
788}
-
789
-
790template <class... Args>
- - - -
794 std::string const& cmd,
-
795 Args&&... args)
-
796{
-
797 return do_rpc(
- -
799 std::vector<std::string>{cmd, std::forward<Args>(args)...},
-
800 headers);
-
801}
-
802
-
803template <class... Args>
- -
805Env::rpc(std::string const& cmd, Args&&... args)
-
806{
-
807 return rpc(
- -
809 cmd,
-
810 std::forward<Args>(args)...);
-
811}
-
812
-
813} // namespace jtx
-
814} // namespace test
-
815} // namespace ripple
-
816
-
817#endif
+
257 virtual ~Env() = default;
+
258
+ + +
261 {
+
262 return *bundle_.app;
+
263 }
+
264
+
265 Application const&
+
266 app() const
+
267 {
+
268 return *bundle_.app;
+
269 }
+
270
+ + +
273 {
+
274 return *bundle_.timeKeeper;
+
275 }
+
276
+ + +
284 {
+
285 return timeKeeper().now();
+
286 }
+
287
+ + +
291 {
+
292 return *bundle_.client;
+
293 }
+
294
+
300 template <class... Args>
+ +
302 rpc(unsigned apiVersion,
+ +
304 std::string const& cmd,
+
305 Args&&... args);
+
306
+
307 template <class... Args>
+ +
309 rpc(unsigned apiVersion, std::string const& cmd, Args&&... args);
+
310
+
311 template <class... Args>
+ + +
314 std::string const& cmd,
+
315 Args&&... args);
+
316
+
317 template <class... Args>
+ +
319 rpc(std::string const& cmd, Args&&... args);
+
320
+ +
330 current() const
+
331 {
+
332 return app().openLedger().current();
+
333 }
+
334
+ +
343 closed();
+
344
+
364 bool
+
365 close(
+
366 NetClock::time_point closeTime,
+
367 std::optional<std::chrono::milliseconds> consensusDelay = std::nullopt);
+
368
+
376 template <class Rep, class Period>
+
377 bool
+ +
379 {
+
380 // VFALCO Is this the correct time?
+
381 return close(now() + elapsed);
+
382 }
+
383
+
391 bool
+ +
393 {
+
394 // VFALCO Is this the correct time?
+
395 return close(std::chrono::seconds(5));
+
396 }
+
397
+
401 void
+
402 trace(int howMany = -1)
+
403 {
+
404 trace_ = howMany;
+
405 }
+
406
+
408 void
+ +
410 {
+
411 trace_ = 0;
+
412 }
+
413
+
414 void
+ +
416 {
+ +
418 }
+
419
+
421 void
+ +
423 {
+
424 app().checkSigs(false);
+
425 }
+
426
+
427 // set rpc retries
+
428 void
+
429 set_retries(unsigned r = 5)
+
430 {
+
431 retries_ = r;
+
432 }
+
433
+
434 // get rpc retries
+
435 unsigned
+
436 retries() const
+
437 {
+
438 return retries_;
+
439 }
+
440
+
442 void
+
443 memoize(Account const& account);
+
444
+
447 Account const&
+
448 lookup(AccountID const& id) const;
+
449
+
450 Account const&
+
451 lookup(std::string const& base58ID) const;
+ +
458 balance(Account const& account) const;
+
459
+ +
465 seq(Account const& account) const;
+
466
+
470 // VFALCO NOTE This should return a unit-less amount
+ +
472 balance(Account const& account, Issue const& issue) const;
+
473
+ +
478 ownerCount(Account const& account) const;
+
479
+ +
484 le(Account const& account) const;
+
485
+ +
490 le(Keylet const& k) const;
+
491
+
493 template <class JsonValue, class... FN>
+
494 JTx
+
495 jt(JsonValue&& jv, FN const&... fN)
+
496 {
+
497 JTx jt(std::forward<JsonValue>(jv));
+
498 invoke(jt, fN...);
+
499 autofill(jt);
+
500 jt.stx = st(jt);
+
501 return jt;
+
502 }
+
503
+
505 template <class JsonValue, class... FN>
+
506 JTx
+
507 jtnofill(JsonValue&& jv, FN const&... fN)
+
508 {
+
509 JTx jt(std::forward<JsonValue>(jv));
+
510 invoke(jt, fN...);
+ +
512 jt.stx = st(jt);
+
513 return jt;
+
514 }
+
515
+
519 template <class JsonValue, class... FN>
+ +
521 json(JsonValue&& jv, FN const&... fN)
+
522 {
+
523 auto tj = jt(std::forward<JsonValue>(jv), fN...);
+
524 return std::move(tj.jv);
+
525 }
+
526
+
532 template <class... Args>
+
533 void
+
534 require(Args const&... args)
+
535 {
+
536 jtx::required(args...)(*this);
+
537 }
+
538
+
541 static ParsedResult
+
542 parseResult(Json::Value const& jr);
+
543
+
547 virtual void
+
548 submit(JTx const& jt);
+
549
+
553 void
+ +
555
+
559 void
+ +
561 JTx const& jt,
+
562 ParsedResult const& parsed,
+
563 Json::Value const& jr = Json::Value());
+
564
+
567 template <class JsonValue, class... FN>
+
568 Env&
+
569 apply(JsonValue&& jv, FN const&... fN)
+
570 {
+
571 submit(jt(std::forward<JsonValue>(jv), fN...));
+
572 return *this;
+
573 }
+
574
+
575 template <class JsonValue, class... FN>
+
576 Env&
+
577 operator()(JsonValue&& jv, FN const&... fN)
+
578 {
+
579 return apply(std::forward<JsonValue>(jv), fN...);
+
580 }
+
584 TER
+
585 ter() const
+
586 {
+
587 return ter_;
+
588 }
+
589
+ +
599 meta();
+
600
+ +
613 tx() const;
+
614
+
615 void
+
616 enableFeature(uint256 const feature);
+
617
+
618 void
+
619 disableFeature(uint256 const feature);
+
620
+
621private:
+
622 void
+
623 fund(bool setDefaultRipple, STAmount const& amount, Account const& account);
+
624
+
625 void
+
626 fund_arg(STAmount const& amount, Account const& account)
+
627 {
+
628 fund(true, amount, account);
+
629 }
+
630
+
631 template <std::size_t N>
+
632 void
+
633 fund_arg(STAmount const& amount, std::array<Account, N> const& list)
+
634 {
+
635 for (auto const& account : list)
+
636 fund(false, amount, account);
+
637 }
+
638
+
639public:
+
666 template <class Arg, class... Args>
+
667 void
+
668 fund(STAmount const& amount, Arg const& arg, Args const&... args)
+
669 {
+
670 fund_arg(amount, arg);
+
671 if constexpr (sizeof...(args) > 0)
+
672 fund(amount, args...);
+
673 }
+
674
+
693 void
+
694 trust(STAmount const& amount, Account const& account);
+
695
+
696 template <class... Accounts>
+
697 void
+ +
699 STAmount const& amount,
+
700 Account const& to0,
+
701 Account const& to1,
+
702 Accounts const&... toN)
+
703 {
+
704 trust(amount, to0);
+
705 trust(amount, to1, toN...);
+
706 }
+ +
714 ust(JTx const& jt);
+
715
+
716protected:
+
717 int trace_ = 0;
+ + + + +
722 unsigned retries_ = 5;
+
723
+ +
725 do_rpc(
+
726 unsigned apiVersion,
+
727 std::vector<std::string> const& args,
+ +
729
+
730 void
+ +
732
+
733 virtual void
+
734 autofill(JTx& jt);
+
735
+ +
744 st(JTx const& jt);
+
745
+
746 // Invoke funclets on stx
+
747 // Note: The STTx may not be modified
+
748 template <class... FN>
+
749 void
+
750 invoke(STTx& stx, FN const&... fN)
+
751 {
+
752 (fN(*this, stx), ...);
+
753 }
+
754
+
755 // Invoke funclets on jt
+
756 template <class... FN>
+
757 void
+
758 invoke(JTx& jt, FN const&... fN)
+
759 {
+
760 (fN(*this, jt), ...);
+
761 }
+
762
+
763 // Map of account IDs to Account
+ +
765};
+
766
+
767template <class... Args>
+ + +
770 unsigned apiVersion,
+ +
772 std::string const& cmd,
+
773 Args&&... args)
+
774{
+
775 return do_rpc(
+
776 apiVersion,
+
777 std::vector<std::string>{cmd, std::forward<Args>(args)...},
+
778 headers);
+
779}
+
780
+
781template <class... Args>
+ +
783Env::rpc(unsigned apiVersion, std::string const& cmd, Args&&... args)
+
784{
+
785 return rpc(
+
786 apiVersion,
+ +
788 cmd,
+
789 std::forward<Args>(args)...);
+
790}
+
791
+
792template <class... Args>
+ + + +
796 std::string const& cmd,
+
797 Args&&... args)
+
798{
+
799 return do_rpc(
+ +
801 std::vector<std::string>{cmd, std::forward<Args>(args)...},
+
802 headers);
+
803}
+
804
+
805template <class... Args>
+ +
807Env::rpc(std::string const& cmd, Args&&... args)
+
808{
+
809 return rpc(
+ +
811 cmd,
+
812 std::forward<Args>(args)...);
+
813}
+
814
+
815} // namespace jtx
+
816} // namespace test
+
817} // namespace ripple
+
818
+
819#endif
Represents a JSON value.
Definition: json_value.h:148
@@ -677,72 +679,72 @@ $(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:120
-
void fund_arg(STAmount const &amount, std::array< Account, N > const &list)
Definition: Env.h:631
-
Json::Value json(JsonValue &&jv, FN const &... fN)
Create JSON from parameters.
Definition: Env.h:519
+
void fund_arg(STAmount const &amount, std::array< Account, N > const &list)
Definition: Env.h:633
+
Json::Value json(JsonValue &&jv, FN const &... fN)
Create JSON from parameters.
Definition: Env.h:521
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:400
+
void trace(int howMany=-1)
Turn on JSON tracing.
Definition: Env.h:402
void disableFeature(uint256 const feature)
Definition: Env.cpp:596
-
bool parseFailureExpected_
Definition: Env.h:719
+
bool parseFailureExpected_
Definition: Env.h:721
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:230
+
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:232
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:762
-
void notrace()
Turn off JSON tracing.
Definition: Env.h:407
-
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:532
-
void trust(STAmount const &amount, Account const &to0, Account const &to1, Accounts const &... toN)
Definition: Env.h:696
- -
unsigned retries() const
Definition: Env.h:434
-
TER ter() const
Return the TER for the last JTx.
Definition: Env.h:583
+
std::unordered_map< AccountID, Account > map_
Definition: Env.h:764
+
void notrace()
Turn off JSON tracing.
Definition: Env.h:409
+
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:534
+
void trust(STAmount const &amount, Account const &to0, Account const &to1, Accounts const &... toN)
Definition: Env.h:698
+ +
unsigned retries() const
Definition: Env.h:436
+
TER ter() const
Return the TER for the last JTx.
Definition: Env.h:585
beast::unit_test::suite & test
Definition: Env.h:122
virtual ~Env()=default
-
bool close(std::chrono::duration< Rep, Period > const &elapsed)
Close and advance the ledger.
Definition: Env.h:376
+
bool close(std::chrono::duration< Rep, Period > const &elapsed)
Close and advance the ledger.
Definition: Env.h:378
AppBundle bundle_
Definition: Env.h:158
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:455
-
void invoke(JTx &jt, FN const &... fN)
Definition: Env.h:756
+
void invoke(JTx &jt, FN const &... fN)
Definition: Env.h:758
Env & operator=(Env const &)=delete
-
Env & operator()(JsonValue &&jv, FN const &... fN)
Definition: Env.h:575
+
Env & operator()(JsonValue &&jv, FN const &... fN)
Definition: Env.h:577
Env(Env const &)=delete
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
void postconditions(JTx const &jt, ParsedResult const &parsed, Json::Value const &jr=Json::Value())
Check expected postconditions of JTx submission.
Definition: Env.cpp:387
- +
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:288
+
AbstractClient & client()
Returns the connected client.
Definition: Env.h:290
void autofill_sig(JTx &jt)
Definition: Env.cpp:461
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:666
+
void fund(STAmount const &amount, Arg const &arg, Args const &... args)
Create a new account with some XRP.
Definition: Env.h:668
Json::Value do_rpc(unsigned apiVersion, std::vector< std::string > const &args, std::unordered_map< std::string, std::string > const &headers={})
Definition: Env.cpp:565
- -
void invoke(STTx &stx, FN const &... fN)
Definition: Env.h:748
+ +
void invoke(STTx &stx, FN const &... fN)
Definition: Env.h:750
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:513
void enableFeature(uint256 const feature)
Definition: Env.cpp:588
-
Env(beast::unit_test::suite &suite_)
Create Env with only the current test suite.
Definition: Env.h:251
+
Env(beast::unit_test::suite &suite_)
Create Env with only the current test suite.
Definition: Env.h:253
Account const & master
Definition: Env.h:124
Account const & lookup(AccountID const &id) const
Returns the Account given the AccountID.
Definition: Env.cpp:158
-
unsigned retries_
Definition: Env.h:720
-
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:493
-
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:281
+
unsigned retries_
Definition: Env.h:722
+
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:495
+
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:283
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:539
-
Application & app()
Definition: Env.h:258
+
Application & app()
Definition: Env.h:260
beast::Journal const journal
Definition: Env.h:161
-
Env(beast::unit_test::suite &suite_, FeatureBitset features)
Create Env with default config and specified features.
Definition: Env.h:213
-
ManualTimeKeeper & timeKeeper()
Definition: Env.h:270
-
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:767
+
ManualTimeKeeper & timeKeeper()
Definition: Env.h:272
+
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:769
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:567
-
void disable_sigs()
Turn off signature checks.
Definition: Env.h:420
-
void set_retries(unsigned r=5)
Definition: Env.h:427
-
bool close()
Close and advance the ledger.
Definition: Env.h:390
+
Env & apply(JsonValue &&jv, FN const &... fN)
Apply funclets and submit.
Definition: Env.h:569
+
void disable_sigs()
Turn off signature checks.
Definition: Env.h:422
+
void set_retries(unsigned r=5)
Definition: Env.h:429
+
bool close()
Close and advance the ledger.
Definition: Env.h:392
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
-
TestStopwatch stopwatch_
Definition: Env.h:716
-
Application const & app() const
Definition: Env.h:264
+
TestStopwatch stopwatch_
Definition: Env.h:718
+
Application const & app() const
Definition: Env.h:266
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:505
-
void set_parse_failure_expected(bool b)
Definition: Env.h:413
+
JTx jtnofill(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:507
+
void set_parse_failure_expected(bool b)
Definition: Env.h:415
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:183
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:624
+
void fund_arg(STAmount const &amount, Account const &account)
Definition: Env.h:626
+
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:213
SuiteLogs(beast::unit_test::suite &suite)
Definition: Env.h:100
beast::unit_test::suite & suite_
Definition: Env.h:97
diff --git a/Env__ss_8h_source.html b/Env__ss_8h_source.html index 3e3d7e84d0..696b91cfae 100644 --- a/Env__ss_8h_source.html +++ b/Env__ss_8h_source.html @@ -171,7 +171,7 @@ $(function() {
A transaction testing environment.
Definition: Env.h:120
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
-
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:493
+
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:495
@ nullValue
'null' value
Definition: json_value.h:37
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
Execution context for applying a JSON transaction.
Definition: JTx.h:45
diff --git a/Env__test_8cpp_source.html b/Env__test_8cpp_source.html index 5780791c1d..a035bab284 100644 --- a/Env__test_8cpp_source.html +++ b/Env__test_8cpp_source.html @@ -1074,18 +1074,18 @@ $(function() {
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment wrapper.
Definition: Env_ss.h:34
A transaction testing environment.
Definition: Env.h:120
-
Json::Value json(JsonValue &&jv, FN const &... fN)
Create JSON from parameters.
Definition: Env.h:519
+
Json::Value json(JsonValue &&jv, FN const &... fN)
Create JSON from parameters.
Definition: Env.h:521
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition: Env.cpp:111
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:212
-
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:532
+
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:534
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:455
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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
Account const & master
Definition: Env.h:124
-
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:493
-
Application & app()
Definition: Env.h:258
-
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:767
+
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:495
+
Application & app()
Definition: Env.h:260
+
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:769
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/ErrorCodes_8cpp_source.html b/ErrorCodes_8cpp_source.html index 23d900c59d..781a16fc05 100644 --- a/ErrorCodes_8cpp_source.html +++ b/ErrorCodes_8cpp_source.html @@ -174,133 +174,134 @@ $(function() {
96 {rpcNOT_SYNCED, "notSynced", "Not synced to the network.", 503},
97 {rpcNO_EVENTS, "noEvents", "Current transport does not support events.", 405},
98 {rpcNO_NETWORK, "noNetwork", "Not synced to the network.", 503},
-
99 {rpcNO_PERMISSION, "noPermission", "You don't have permission for this command.", 401},
-
100 {rpcNO_PF_REQUEST, "noPathRequest", "No pathfinding request in progress.", 404},
-
101 {rpcOBJECT_NOT_FOUND, "objectNotFound", "The requested object was not found.", 404},
-
102 {rpcPUBLIC_MALFORMED, "publicMalformed", "Public key is malformed.", 400},
-
103 {rpcSENDMAX_MALFORMED, "sendMaxMalformed", "SendMax amount malformed.", 400},
-
104 {rpcSIGNING_MALFORMED, "signingMalformed", "Signing of transaction is malformed.", 400},
-
105 {rpcSLOW_DOWN, "slowDown", "You are placing too much load on the server.", 429},
-
106 {rpcSRC_ACT_MALFORMED, "srcActMalformed", "Source account is malformed.", 400},
-
107 {rpcSRC_ACT_MISSING, "srcActMissing", "Source account not provided.", 400},
-
108 {rpcSRC_ACT_NOT_FOUND, "srcActNotFound", "Source account not found.", 404},
-
109 {rpcSRC_CUR_MALFORMED, "srcCurMalformed", "Source currency is malformed.", 400},
-
110 {rpcSRC_ISR_MALFORMED, "srcIsrMalformed", "Source issuer is malformed.", 400},
-
111 {rpcSTREAM_MALFORMED, "malformedStream", "Stream malformed.", 400},
-
112 {rpcTOO_BUSY, "tooBusy", "The server is too busy to help you now.", 503},
-
113 {rpcTXN_NOT_FOUND, "txnNotFound", "Transaction not found.", 404},
-
114 {rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown method.", 405},
-
115 {rpcORACLE_MALFORMED, "oracleMalformed", "Oracle request is malformed.", 400},
-
116 {rpcBAD_CREDENTIALS, "badCredentials", "Credentials do not exist, are not accepted, or have expired.", 400},
-
117 {rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400}};
-
118// clang-format on
-
119
-
120// Sort and validate unorderedErrorInfos at compile time. Should be
-
121// converted to consteval when get to C++20.
-
122template <int M, int N>
-
123constexpr auto
- -
125{
- -
127
-
128 for (ErrorInfo const& info : unordered)
-
129 {
-
130 if (info.code <= rpcSUCCESS || info.code > rpcLAST)
-
131 throw(std::out_of_range("Invalid error_code_i"));
-
132
-
133 // The first valid code follows rpcSUCCESS immediately.
-
134 static_assert(rpcSUCCESS == 0, "Unexpected error_code_i layout.");
-
135 int const index{info.code - 1};
-
136
-
137 if (ret[index].code != rpcUNKNOWN)
-
138 throw(std::invalid_argument("Duplicate error_code_i in list"));
-
139
-
140 ret[index] = info;
-
141 }
-
142
-
143 // Verify that all entries are filled in starting with 1 and proceeding
-
144 // to rpcLAST.
-
145 //
-
146 // It's okay for there to be missing entries; they will contain the code
-
147 // rpcUNKNOWN. But other than that all entries should match their index.
-
148 int codeCount{0};
-
149 int expect{rpcBAD_SYNTAX - 1};
-
150 for (ErrorInfo const& info : ret)
-
151 {
-
152 ++expect;
-
153 if (info.code == rpcUNKNOWN)
-
154 continue;
-
155
-
156 if (info.code != expect)
-
157 throw(std::invalid_argument("Empty error_code_i in list"));
-
158 ++codeCount;
-
159 }
-
160 if (expect != rpcLAST)
-
161 throw(std::invalid_argument("Insufficient list entries"));
-
162 if (codeCount != N)
-
163 throw(std::invalid_argument("Bad handling of unorderedErrorInfos"));
-
164
-
165 return ret;
-
166}
-
167
-
168constexpr auto sortedErrorInfos{sortErrorInfos<rpcLAST>(unorderedErrorInfos)};
-
169
- -
171
-
172} // namespace detail
-
173
-
174//------------------------------------------------------------------------------
-
175
-
176ErrorInfo const&
- -
178{
-
179 if (code <= rpcSUCCESS || code > rpcLAST)
- -
181 return detail::sortedErrorInfos[code - 1];
-
182}
-
183
- - -
186{
-
187 Json::Value json;
-
188 inject_error(code, json);
-
189 return json;
-
190}
-
191
- - -
194{
-
195 Json::Value json;
-
196 inject_error(code, message, json);
-
197 return json;
-
198}
-
199
-
200bool
- -
202{
-
203 if (json.isObject() && json.isMember(jss::error))
-
204 return true;
-
205 return false;
-
206}
-
207
-
208int
- -
210{
-
211 return get_error_info(code).http_status;
-
212}
-
213
-
214} // namespace RPC
-
215
- - -
218{
-
219 XRPL_ASSERT(
- -
221 "ripple::RPC::rpcErrorString : input contains an error");
-
222 return jv[jss::error].asString() + jv[jss::error_message].asString();
-
223}
-
224
-
225} // namespace ripple
+
99 {rpcWRONG_NETWORK, "wrongNetwork", "Wrong network.", 503},
+
100 {rpcNO_PERMISSION, "noPermission", "You don't have permission for this command.", 401},
+
101 {rpcNO_PF_REQUEST, "noPathRequest", "No pathfinding request in progress.", 404},
+
102 {rpcOBJECT_NOT_FOUND, "objectNotFound", "The requested object was not found.", 404},
+
103 {rpcPUBLIC_MALFORMED, "publicMalformed", "Public key is malformed.", 400},
+
104 {rpcSENDMAX_MALFORMED, "sendMaxMalformed", "SendMax amount malformed.", 400},
+
105 {rpcSIGNING_MALFORMED, "signingMalformed", "Signing of transaction is malformed.", 400},
+
106 {rpcSLOW_DOWN, "slowDown", "You are placing too much load on the server.", 429},
+
107 {rpcSRC_ACT_MALFORMED, "srcActMalformed", "Source account is malformed.", 400},
+
108 {rpcSRC_ACT_MISSING, "srcActMissing", "Source account not provided.", 400},
+
109 {rpcSRC_ACT_NOT_FOUND, "srcActNotFound", "Source account not found.", 404},
+
110 {rpcSRC_CUR_MALFORMED, "srcCurMalformed", "Source currency is malformed.", 400},
+
111 {rpcSRC_ISR_MALFORMED, "srcIsrMalformed", "Source issuer is malformed.", 400},
+
112 {rpcSTREAM_MALFORMED, "malformedStream", "Stream malformed.", 400},
+
113 {rpcTOO_BUSY, "tooBusy", "The server is too busy to help you now.", 503},
+
114 {rpcTXN_NOT_FOUND, "txnNotFound", "Transaction not found.", 404},
+
115 {rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown method.", 405},
+
116 {rpcORACLE_MALFORMED, "oracleMalformed", "Oracle request is malformed.", 400},
+
117 {rpcBAD_CREDENTIALS, "badCredentials", "Credentials do not exist, are not accepted, or have expired.", 400},
+
118 {rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400}};
+
119// clang-format on
+
120
+
121// Sort and validate unorderedErrorInfos at compile time. Should be
+
122// converted to consteval when get to C++20.
+
123template <int M, int N>
+
124constexpr auto
+ +
126{
+ +
128
+
129 for (ErrorInfo const& info : unordered)
+
130 {
+
131 if (info.code <= rpcSUCCESS || info.code > rpcLAST)
+
132 throw(std::out_of_range("Invalid error_code_i"));
+
133
+
134 // The first valid code follows rpcSUCCESS immediately.
+
135 static_assert(rpcSUCCESS == 0, "Unexpected error_code_i layout.");
+
136 int const index{info.code - 1};
+
137
+
138 if (ret[index].code != rpcUNKNOWN)
+
139 throw(std::invalid_argument("Duplicate error_code_i in list"));
+
140
+
141 ret[index] = info;
+
142 }
+
143
+
144 // Verify that all entries are filled in starting with 1 and proceeding
+
145 // to rpcLAST.
+
146 //
+
147 // It's okay for there to be missing entries; they will contain the code
+
148 // rpcUNKNOWN. But other than that all entries should match their index.
+
149 int codeCount{0};
+
150 int expect{rpcBAD_SYNTAX - 1};
+
151 for (ErrorInfo const& info : ret)
+
152 {
+
153 ++expect;
+
154 if (info.code == rpcUNKNOWN)
+
155 continue;
+
156
+
157 if (info.code != expect)
+
158 throw(std::invalid_argument("Empty error_code_i in list"));
+
159 ++codeCount;
+
160 }
+
161 if (expect != rpcLAST)
+
162 throw(std::invalid_argument("Insufficient list entries"));
+
163 if (codeCount != N)
+
164 throw(std::invalid_argument("Bad handling of unorderedErrorInfos"));
+
165
+
166 return ret;
+
167}
+
168
+
169constexpr auto sortedErrorInfos{sortErrorInfos<rpcLAST>(unorderedErrorInfos)};
+
170
+ +
172
+
173} // namespace detail
+
174
+
175//------------------------------------------------------------------------------
+
176
+
177ErrorInfo const&
+ +
179{
+
180 if (code <= rpcSUCCESS || code > rpcLAST)
+ +
182 return detail::sortedErrorInfos[code - 1];
+
183}
+
184
+ + +
187{
+
188 Json::Value json;
+
189 inject_error(code, json);
+
190 return json;
+
191}
+
192
+ + +
195{
+
196 Json::Value json;
+
197 inject_error(code, message, json);
+
198 return json;
+
199}
+
200
+
201bool
+ +
203{
+
204 if (json.isObject() && json.isMember(jss::error))
+
205 return true;
+
206 return false;
+
207}
+
208
+
209int
+ +
211{
+
212 return get_error_info(code).http_status;
+
213}
+
214
+
215} // namespace RPC
+
216
+ + +
219{
+
220 XRPL_ASSERT(
+ +
222 "ripple::RPC::rpcErrorString : input contains an error");
+
223 return jv[jss::error].asString() + jv[jss::error_message].asString();
+
224}
+
225
+
226} // namespace ripple
Represents a JSON value.
Definition: json_value.h:148
@@ -308,17 +309,17 @@ $(function() {
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:475
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:949
-
constexpr auto sortedErrorInfos
Definition: ErrorCodes.cpp:168
-
constexpr ErrorInfo unknownError
Definition: ErrorCodes.cpp:170
-
constexpr auto sortErrorInfos(ErrorInfo const (&unordered)[N]) -> std::array< ErrorInfo, M >
Definition: ErrorCodes.cpp:124
+
constexpr auto sortedErrorInfos
Definition: ErrorCodes.cpp:169
+
constexpr ErrorInfo unknownError
Definition: ErrorCodes.cpp:171
+
constexpr auto sortErrorInfos(ErrorInfo const (&unordered)[N]) -> std::array< ErrorInfo, M >
Definition: ErrorCodes.cpp:125
static constexpr ErrorInfo unorderedErrorInfos[]
Definition: ErrorCodes.cpp:51
-
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:201
-
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:185
+
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:202
+
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:186
void inject_error(error_code_i code, JsonValue &json)
Add or update the json update to reflect the error code.
Definition: ErrorCodes.h:223
-
int error_code_http_status(error_code_i code)
Returns http status that corresponds to the error code.
Definition: ErrorCodes.cpp:209
-
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:177
+
int error_code_http_status(error_code_i code)
Returns http status that corresponds to the error code.
Definition: ErrorCodes.cpp:210
+
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:178
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
-
std::string rpcErrorString(Json::Value const &jv)
Returns a single string with the contents of an RPC error.
Definition: ErrorCodes.cpp:217
+
std::string rpcErrorString(Json::Value const &jv)
Returns a single string with the contents of an RPC error.
Definition: ErrorCodes.cpp:218
error_code_i
Definition: ErrorCodes.h:40
@ rpcNO_EVENTS
Definition: ErrorCodes.h:54
@ rpcACT_NOT_FOUND
Definition: ErrorCodes.h:70
@@ -379,6 +380,7 @@ $(function() {
@ rpcJSON_RPC
Definition: ErrorCodes.h:47
@ rpcNOT_ENABLED
Definition: ErrorCodes.h:59
@ rpcDST_ACT_MALFORMED
Definition: ErrorCodes.h:103
+
@ rpcWRONG_NETWORK
Definition: ErrorCodes.h:50
@ rpcSRC_CUR_MALFORMED
Definition: ErrorCodes.h:124
@ rpcINVALID_HOTWALLET
Definition: ErrorCodes.h:81
@ rpcBAD_SEED
Definition: ErrorCodes.h:99
diff --git a/ErrorCodes_8h_source.html b/ErrorCodes_8h_source.html index 9e46d8305b..adad47f837 100644 --- a/ErrorCodes_8h_source.html +++ b/ErrorCodes_8h_source.html @@ -432,23 +432,23 @@ $(function() {
Lightweight wrapper to tag static string.
Definition: json_value.h:62
Represents a JSON value.
Definition: json_value.h:148
-
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:201
-
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:185
+
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:202
+
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:186
Json::Value invalid_field_error(std::string const &name)
Definition: ErrorCodes.h:315
void inject_error(error_code_i code, JsonValue &json)
Add or update the json update to reflect the error code.
Definition: ErrorCodes.h:223
Json::Value make_param_error(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition: ErrorCodes.h:261
std::string missing_field_message(std::string const &name)
Definition: ErrorCodes.h:267
Json::Value not_validator_error()
Definition: ErrorCodes.h:351
-
int error_code_http_status(error_code_i code)
Returns http status that corresponds to the error code.
Definition: ErrorCodes.cpp:209
+
int error_code_http_status(error_code_i code)
Returns http status that corresponds to the error code.
Definition: ErrorCodes.cpp:210
std::string invalid_field_message(std::string const &name)
Definition: ErrorCodes.h:303
std::string expected_field_message(std::string const &name, std::string const &type)
Definition: ErrorCodes.h:327
Json::Value object_field_error(std::string const &name)
Definition: ErrorCodes.h:291
std::string object_field_message(std::string const &name)
Definition: ErrorCodes.h:285
-
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:177
+
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:178
Json::Value expected_field_error(std::string const &name, std::string const &type)
Definition: ErrorCodes.h:339
Json::Value missing_field_error(std::string const &name)
Definition: ErrorCodes.h:273
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
-
std::string rpcErrorString(Json::Value const &jv)
Returns a single string with the contents of an RPC error.
Definition: ErrorCodes.cpp:217
+
std::string rpcErrorString(Json::Value const &jv)
Returns a single string with the contents of an RPC error.
Definition: ErrorCodes.cpp:218
error_code_i
Definition: ErrorCodes.h:40
@ rpcNO_EVENTS
Definition: ErrorCodes.h:54
@ rpcACT_NOT_FOUND
Definition: ErrorCodes.h:70
diff --git a/Escrow_8cpp_source.html b/Escrow_8cpp_source.html index 8300a2f8a9..afbd81e574 100644 --- a/Escrow_8cpp_source.html +++ b/Escrow_8cpp_source.html @@ -154,525 +154,519 @@ $(function() {
76
77//------------------------------------------------------------------------------
78
-
85static inline bool
- +
79TxConsequences
+ +
81{
+
82 return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
+
83}
+
84
+ +
87{
-
88 return now.time_since_epoch().count() > mark;
-
89}
+
88 if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
+
89 return temINVALID_FLAG;
90
-
91TxConsequences
- -
93{
-
94 return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
-
95}
+
91 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
+
92 return ret;
+
93
+
94 if (!isXRP(ctx.tx[sfAmount]))
+
95 return temBAD_AMOUNT;
96
- - -
99{
-
100 if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
-
101 return temINVALID_FLAG;
-
102
-
103 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
-
104 return ret;
-
105
-
106 if (!isXRP(ctx.tx[sfAmount]))
-
107 return temBAD_AMOUNT;
-
108
-
109 if (ctx.tx[sfAmount] <= beast::zero)
-
110 return temBAD_AMOUNT;
-
111
-
112 // We must specify at least one timeout value
-
113 if (!ctx.tx[~sfCancelAfter] && !ctx.tx[~sfFinishAfter])
-
114 return temBAD_EXPIRATION;
-
115
-
116 // If both finish and cancel times are specified then the cancel time must
-
117 // be strictly after the finish time.
-
118 if (ctx.tx[~sfCancelAfter] && ctx.tx[~sfFinishAfter] &&
-
119 ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
-
120 return temBAD_EXPIRATION;
-
121
-
122 if (ctx.rules.enabled(fix1571))
-
123 {
-
124 // In the absence of a FinishAfter, the escrow can be finished
-
125 // immediately, which can be confusing. When creating an escrow,
-
126 // we want to ensure that either a FinishAfter time is explicitly
-
127 // specified or a completion condition is attached.
-
128 if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition])
-
129 return temMALFORMED;
-
130 }
-
131
-
132 if (auto const cb = ctx.tx[~sfCondition])
-
133 {
-
134 using namespace ripple::cryptoconditions;
-
135
- -
137
-
138 auto condition = Condition::deserialize(*cb, ec);
-
139 if (!condition)
-
140 {
-
141 JLOG(ctx.j.debug())
-
142 << "Malformed condition during escrow creation: "
-
143 << ec.message();
-
144 return temMALFORMED;
-
145 }
-
146
-
147 // Conditions other than PrefixSha256 require the
-
148 // "CryptoConditionsSuite" amendment:
-
149 if (condition->type != Type::preimageSha256 &&
-
150 !ctx.rules.enabled(featureCryptoConditionsSuite))
-
151 return temDISABLED;
-
152 }
+
97 if (ctx.tx[sfAmount] <= beast::zero)
+
98 return temBAD_AMOUNT;
+
99
+
100 // We must specify at least one timeout value
+
101 if (!ctx.tx[~sfCancelAfter] && !ctx.tx[~sfFinishAfter])
+
102 return temBAD_EXPIRATION;
+
103
+
104 // If both finish and cancel times are specified then the cancel time must
+
105 // be strictly after the finish time.
+
106 if (ctx.tx[~sfCancelAfter] && ctx.tx[~sfFinishAfter] &&
+
107 ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
+
108 return temBAD_EXPIRATION;
+
109
+
110 if (ctx.rules.enabled(fix1571))
+
111 {
+
112 // In the absence of a FinishAfter, the escrow can be finished
+
113 // immediately, which can be confusing. When creating an escrow,
+
114 // we want to ensure that either a FinishAfter time is explicitly
+
115 // specified or a completion condition is attached.
+
116 if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition])
+
117 return temMALFORMED;
+
118 }
+
119
+
120 if (auto const cb = ctx.tx[~sfCondition])
+
121 {
+
122 using namespace ripple::cryptoconditions;
+
123
+ +
125
+
126 auto condition = Condition::deserialize(*cb, ec);
+
127 if (!condition)
+
128 {
+
129 JLOG(ctx.j.debug())
+
130 << "Malformed condition during escrow creation: "
+
131 << ec.message();
+
132 return temMALFORMED;
+
133 }
+
134
+
135 // Conditions other than PrefixSha256 require the
+
136 // "CryptoConditionsSuite" amendment:
+
137 if (condition->type != Type::preimageSha256 &&
+
138 !ctx.rules.enabled(featureCryptoConditionsSuite))
+
139 return temDISABLED;
+
140 }
+
141
+
142 return preflight2(ctx);
+
143}
+
144
+
145TER
+ +
147{
+
148 auto const sled = ctx.view.read(keylet::account(ctx.tx[sfDestination]));
+
149 if (!sled)
+
150 return tecNO_DST;
+
151 if (sled->isFieldPresent(sfAMMID))
+
152 return tecNO_PERMISSION;
153
-
154 return preflight2(ctx);
+
154 return tesSUCCESS;
155}
156
157TER
- +
159{
-
160 auto const sled = ctx.view.read(keylet::account(ctx.tx[sfDestination]));
-
161 if (!sled)
-
162 return tecNO_DST;
-
163 if (sled->isFieldPresent(sfAMMID))
-
164 return tecNO_PERMISSION;
-
165
-
166 return tesSUCCESS;
-
167}
-
168
-
169TER
- -
171{
-
172 auto const closeTime = ctx_.view().info().parentCloseTime;
-
173
-
174 // Prior to fix1571, the cancel and finish times could be greater
-
175 // than or equal to the parent ledgers' close time.
-
176 //
-
177 // With fix1571, we require that they both be strictly greater
-
178 // than the parent ledgers' close time.
-
179 if (ctx_.view().rules().enabled(fix1571))
-
180 {
-
181 if (ctx_.tx[~sfCancelAfter] && after(closeTime, ctx_.tx[sfCancelAfter]))
-
182 return tecNO_PERMISSION;
-
183
-
184 if (ctx_.tx[~sfFinishAfter] && after(closeTime, ctx_.tx[sfFinishAfter]))
-
185 return tecNO_PERMISSION;
-
186 }
-
187 else
-
188 {
-
189 if (ctx_.tx[~sfCancelAfter])
-
190 {
-
191 auto const cancelAfter = ctx_.tx[sfCancelAfter];
-
192
-
193 if (closeTime.time_since_epoch().count() >= cancelAfter)
-
194 return tecNO_PERMISSION;
-
195 }
-
196
-
197 if (ctx_.tx[~sfFinishAfter])
-
198 {
-
199 auto const finishAfter = ctx_.tx[sfFinishAfter];
-
200
-
201 if (closeTime.time_since_epoch().count() >= finishAfter)
-
202 return tecNO_PERMISSION;
-
203 }
-
204 }
-
205
-
206 auto const account = ctx_.tx[sfAccount];
-
207 auto const sle = ctx_.view().peek(keylet::account(account));
-
208 if (!sle)
-
209 return tefINTERNAL;
-
210
-
211 // Check reserve and funds availability
-
212 {
-
213 auto const balance = STAmount((*sle)[sfBalance]).xrp();
-
214 auto const reserve =
-
215 ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
-
216
-
217 if (balance < reserve)
- -
219
-
220 if (balance < reserve + STAmount(ctx_.tx[sfAmount]).xrp())
-
221 return tecUNFUNDED;
-
222 }
-
223
-
224 // Check destination account
-
225 {
-
226 auto const sled =
-
227 ctx_.view().read(keylet::account(ctx_.tx[sfDestination]));
-
228 if (!sled)
-
229 return tecNO_DST;
-
230 if (((*sled)[sfFlags] & lsfRequireDestTag) &&
-
231 !ctx_.tx[~sfDestinationTag])
-
232 return tecDST_TAG_NEEDED;
-
233
-
234 // Obeying the lsfDissalowXRP flag was a bug. Piggyback on
-
235 // featureDepositAuth to remove the bug.
-
236 if (!ctx_.view().rules().enabled(featureDepositAuth) &&
-
237 ((*sled)[sfFlags] & lsfDisallowXRP))
-
238 return tecNO_TARGET;
-
239 }
-
240
-
241 // Create escrow in ledger. Note that we we use the value from the
-
242 // sequence or ticket. For more explanation see comments in SeqProxy.h.
-
243 Keylet const escrowKeylet =
-
244 keylet::escrow(account, ctx_.tx.getSeqProxy().value());
-
245 auto const slep = std::make_shared<SLE>(escrowKeylet);
-
246 (*slep)[sfAmount] = ctx_.tx[sfAmount];
-
247 (*slep)[sfAccount] = account;
-
248 (*slep)[~sfCondition] = ctx_.tx[~sfCondition];
-
249 (*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
-
250 (*slep)[sfDestination] = ctx_.tx[sfDestination];
-
251 (*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
-
252 (*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
-
253 (*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
-
254
-
255 ctx_.view().insert(slep);
-
256
-
257 // Add escrow to sender's owner directory
-
258 {
-
259 auto page = ctx_.view().dirInsert(
-
260 keylet::ownerDir(account), escrowKeylet, describeOwnerDir(account));
-
261 if (!page)
-
262 return tecDIR_FULL;
-
263 (*slep)[sfOwnerNode] = *page;
-
264 }
-
265
-
266 // If it's not a self-send, add escrow to recipient's owner directory.
-
267 if (auto const dest = ctx_.tx[sfDestination]; dest != ctx_.tx[sfAccount])
-
268 {
-
269 auto page = ctx_.view().dirInsert(
-
270 keylet::ownerDir(dest), escrowKeylet, describeOwnerDir(dest));
-
271 if (!page)
-
272 return tecDIR_FULL;
-
273 (*slep)[sfDestinationNode] = *page;
-
274 }
-
275
-
276 // Deduct owner's balance, increment owner count
-
277 (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
- -
279 ctx_.view().update(sle);
+
160 auto const closeTime = ctx_.view().info().parentCloseTime;
+
161
+
162 // Prior to fix1571, the cancel and finish times could be greater
+
163 // than or equal to the parent ledgers' close time.
+
164 //
+
165 // With fix1571, we require that they both be strictly greater
+
166 // than the parent ledgers' close time.
+
167 if (ctx_.view().rules().enabled(fix1571))
+
168 {
+
169 if (ctx_.tx[~sfCancelAfter] && after(closeTime, ctx_.tx[sfCancelAfter]))
+
170 return tecNO_PERMISSION;
+
171
+
172 if (ctx_.tx[~sfFinishAfter] && after(closeTime, ctx_.tx[sfFinishAfter]))
+
173 return tecNO_PERMISSION;
+
174 }
+
175 else
+
176 {
+
177 if (ctx_.tx[~sfCancelAfter])
+
178 {
+
179 auto const cancelAfter = ctx_.tx[sfCancelAfter];
+
180
+
181 if (closeTime.time_since_epoch().count() >= cancelAfter)
+
182 return tecNO_PERMISSION;
+
183 }
+
184
+
185 if (ctx_.tx[~sfFinishAfter])
+
186 {
+
187 auto const finishAfter = ctx_.tx[sfFinishAfter];
+
188
+
189 if (closeTime.time_since_epoch().count() >= finishAfter)
+
190 return tecNO_PERMISSION;
+
191 }
+
192 }
+
193
+
194 auto const account = ctx_.tx[sfAccount];
+
195 auto const sle = ctx_.view().peek(keylet::account(account));
+
196 if (!sle)
+
197 return tefINTERNAL;
+
198
+
199 // Check reserve and funds availability
+
200 {
+
201 auto const balance = STAmount((*sle)[sfBalance]).xrp();
+
202 auto const reserve =
+
203 ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
+
204
+
205 if (balance < reserve)
+ +
207
+
208 if (balance < reserve + STAmount(ctx_.tx[sfAmount]).xrp())
+
209 return tecUNFUNDED;
+
210 }
+
211
+
212 // Check destination account
+
213 {
+
214 auto const sled =
+
215 ctx_.view().read(keylet::account(ctx_.tx[sfDestination]));
+
216 if (!sled)
+
217 return tecNO_DST;
+
218 if (((*sled)[sfFlags] & lsfRequireDestTag) &&
+
219 !ctx_.tx[~sfDestinationTag])
+
220 return tecDST_TAG_NEEDED;
+
221
+
222 // Obeying the lsfDissalowXRP flag was a bug. Piggyback on
+
223 // featureDepositAuth to remove the bug.
+
224 if (!ctx_.view().rules().enabled(featureDepositAuth) &&
+
225 ((*sled)[sfFlags] & lsfDisallowXRP))
+
226 return tecNO_TARGET;
+
227 }
+
228
+
229 // Create escrow in ledger. Note that we we use the value from the
+
230 // sequence or ticket. For more explanation see comments in SeqProxy.h.
+
231 Keylet const escrowKeylet =
+
232 keylet::escrow(account, ctx_.tx.getSeqProxy().value());
+
233 auto const slep = std::make_shared<SLE>(escrowKeylet);
+
234 (*slep)[sfAmount] = ctx_.tx[sfAmount];
+
235 (*slep)[sfAccount] = account;
+
236 (*slep)[~sfCondition] = ctx_.tx[~sfCondition];
+
237 (*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
+
238 (*slep)[sfDestination] = ctx_.tx[sfDestination];
+
239 (*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
+
240 (*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
+
241 (*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
+
242
+
243 ctx_.view().insert(slep);
+
244
+
245 // Add escrow to sender's owner directory
+
246 {
+
247 auto page = ctx_.view().dirInsert(
+
248 keylet::ownerDir(account), escrowKeylet, describeOwnerDir(account));
+
249 if (!page)
+
250 return tecDIR_FULL;
+
251 (*slep)[sfOwnerNode] = *page;
+
252 }
+
253
+
254 // If it's not a self-send, add escrow to recipient's owner directory.
+
255 if (auto const dest = ctx_.tx[sfDestination]; dest != ctx_.tx[sfAccount])
+
256 {
+
257 auto page = ctx_.view().dirInsert(
+
258 keylet::ownerDir(dest), escrowKeylet, describeOwnerDir(dest));
+
259 if (!page)
+
260 return tecDIR_FULL;
+
261 (*slep)[sfDestinationNode] = *page;
+
262 }
+
263
+
264 // Deduct owner's balance, increment owner count
+
265 (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
+ +
267 ctx_.view().update(sle);
+
268
+
269 return tesSUCCESS;
+
270}
+
271
+
272//------------------------------------------------------------------------------
+
273
+
274static bool
+ +
276{
+
277 using namespace ripple::cryptoconditions;
+
278
+
280
-
281 return tesSUCCESS;
-
282}
-
283
-
284//------------------------------------------------------------------------------
-
285
-
286static bool
- -
288{
-
289 using namespace ripple::cryptoconditions;
-
290
- -
292
-
293 auto condition = Condition::deserialize(c, ec);
-
294 if (!condition)
-
295 return false;
-
296
-
297 auto fulfillment = Fulfillment::deserialize(f, ec);
-
298 if (!fulfillment)
-
299 return false;
-
300
-
301 return validate(*fulfillment, *condition);
-
302}
-
303
-
304NotTEC
- -
306{
-
307 if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
-
308 return temINVALID_FLAG;
-
309
-
310 if (ctx.tx.isFieldPresent(sfCredentialIDs) &&
-
311 !ctx.rules.enabled(featureCredentials))
-
312 return temDISABLED;
-
313
-
314 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
-
315 return ret;
-
316
-
317 auto const cb = ctx.tx[~sfCondition];
-
318 auto const fb = ctx.tx[~sfFulfillment];
-
319
-
320 // If you specify a condition, then you must also specify
-
321 // a fulfillment.
-
322 if (static_cast<bool>(cb) != static_cast<bool>(fb))
-
323 return temMALFORMED;
+
281 auto condition = Condition::deserialize(c, ec);
+
282 if (!condition)
+
283 return false;
+
284
+
285 auto fulfillment = Fulfillment::deserialize(f, ec);
+
286 if (!fulfillment)
+
287 return false;
+
288
+
289 return validate(*fulfillment, *condition);
+
290}
+
291
+
292NotTEC
+ +
294{
+
295 if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
+
296 return temINVALID_FLAG;
+
297
+
298 if (ctx.tx.isFieldPresent(sfCredentialIDs) &&
+
299 !ctx.rules.enabled(featureCredentials))
+
300 return temDISABLED;
+
301
+
302 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
+
303 return ret;
+
304
+
305 auto const cb = ctx.tx[~sfCondition];
+
306 auto const fb = ctx.tx[~sfFulfillment];
+
307
+
308 // If you specify a condition, then you must also specify
+
309 // a fulfillment.
+
310 if (static_cast<bool>(cb) != static_cast<bool>(fb))
+
311 return temMALFORMED;
+
312
+
313 // Verify the transaction signature. If it doesn't work
+
314 // then don't do any more work.
+
315 {
+
316 auto const ret = preflight2(ctx);
+
317 if (!isTesSuccess(ret))
+
318 return ret;
+
319 }
+
320
+
321 if (cb && fb)
+
322 {
+
323 auto& router = ctx.app.getHashRouter();
324
-
325 // Verify the transaction signature. If it doesn't work
-
326 // then don't do any more work.
-
327 {
-
328 auto const ret = preflight2(ctx);
-
329 if (!isTesSuccess(ret))
-
330 return ret;
-
331 }
-
332
-
333 if (cb && fb)
-
334 {
-
335 auto& router = ctx.app.getHashRouter();
-
336
-
337 auto const id = ctx.tx.getTransactionID();
-
338 auto const flags = router.getFlags(id);
+
325 auto const id = ctx.tx.getTransactionID();
+
326 auto const flags = router.getFlags(id);
+
327
+
328 // If we haven't checked the condition, check it
+
329 // now. Whether it passes or not isn't important
+
330 // in preflight.
+
331 if (!(flags & (SF_CF_INVALID | SF_CF_VALID)))
+
332 {
+
333 if (checkCondition(*fb, *cb))
+
334 router.setFlags(id, SF_CF_VALID);
+
335 else
+
336 router.setFlags(id, SF_CF_INVALID);
+
337 }
+
338 }
339
-
340 // If we haven't checked the condition, check it
-
341 // now. Whether it passes or not isn't important
-
342 // in preflight.
-
343 if (!(flags & (SF_CF_INVALID | SF_CF_VALID)))
-
344 {
-
345 if (checkCondition(*fb, *cb))
-
346 router.setFlags(id, SF_CF_VALID);
-
347 else
-
348 router.setFlags(id, SF_CF_INVALID);
-
349 }
-
350 }
-
351
-
352 if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err))
-
353 return err;
-
354
-
355 return tesSUCCESS;
-
356}
-
357
- - -
360{
-
361 XRPAmount extraFee{0};
-
362
-
363 if (auto const fb = tx[~sfFulfillment])
-
364 {
-
365 extraFee += view.fees().base * (32 + (fb->size() / 16));
-
366 }
-
367
-
368 return Transactor::calculateBaseFee(view, tx) + extraFee;
-
369}
-
370
-
371TER
- -
373{
-
374 if (!ctx.view.rules().enabled(featureCredentials))
-
375 return Transactor::preclaim(ctx);
-
376
-
377 if (auto const err = credentials::valid(ctx, ctx.tx[sfAccount]);
-
378 !isTesSuccess(err))
-
379 return err;
-
380
-
381 return tesSUCCESS;
-
382}
-
383
-
384TER
- -
386{
-
387 auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
-
388 auto const slep = ctx_.view().peek(k);
-
389 if (!slep)
-
390 return tecNO_TARGET;
-
391
-
392 // If a cancel time is present, a finish operation should only succeed prior
-
393 // to that time. fix1571 corrects a logic error in the check that would make
-
394 // a finish only succeed strictly after the cancel time.
-
395 if (ctx_.view().rules().enabled(fix1571))
+
340 if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err))
+
341 return err;
+
342
+
343 return tesSUCCESS;
+
344}
+
345
+ + +
348{
+
349 XRPAmount extraFee{0};
+
350
+
351 if (auto const fb = tx[~sfFulfillment])
+
352 {
+
353 extraFee += view.fees().base * (32 + (fb->size() / 16));
+
354 }
+
355
+
356 return Transactor::calculateBaseFee(view, tx) + extraFee;
+
357}
+
358
+
359TER
+ +
361{
+
362 if (!ctx.view.rules().enabled(featureCredentials))
+
363 return Transactor::preclaim(ctx);
+
364
+
365 if (auto const err = credentials::valid(ctx, ctx.tx[sfAccount]);
+
366 !isTesSuccess(err))
+
367 return err;
+
368
+
369 return tesSUCCESS;
+
370}
+
371
+
372TER
+ +
374{
+
375 auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
+
376 auto const slep = ctx_.view().peek(k);
+
377 if (!slep)
+
378 return tecNO_TARGET;
+
379
+
380 // If a cancel time is present, a finish operation should only succeed prior
+
381 // to that time. fix1571 corrects a logic error in the check that would make
+
382 // a finish only succeed strictly after the cancel time.
+
383 if (ctx_.view().rules().enabled(fix1571))
+
384 {
+
385 auto const now = ctx_.view().info().parentCloseTime;
+
386
+
387 // Too soon: can't execute before the finish time
+
388 if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
+
389 return tecNO_PERMISSION;
+
390
+
391 // Too late: can't execute after the cancel time
+
392 if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
+
393 return tecNO_PERMISSION;
+
394 }
+
395 else
396 {
-
397 auto const now = ctx_.view().info().parentCloseTime;
-
398
-
399 // Too soon: can't execute before the finish time
-
400 if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
+
397 // Too soon?
+
398 if ((*slep)[~sfFinishAfter] &&
+ +
400 (*slep)[sfFinishAfter])
401 return tecNO_PERMISSION;
402
-
403 // Too late: can't execute after the cancel time
-
404 if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
-
405 return tecNO_PERMISSION;
-
406 }
-
407 else
-
408 {
-
409 // Too soon?
-
410 if ((*slep)[~sfFinishAfter] &&
- -
412 (*slep)[sfFinishAfter])
-
413 return tecNO_PERMISSION;
+
403 // Too late?
+
404 if ((*slep)[~sfCancelAfter] &&
+ +
406 (*slep)[sfCancelAfter])
+
407 return tecNO_PERMISSION;
+
408 }
+
409
+
410 // Check cryptocondition fulfillment
+
411 {
+
412 auto const id = ctx_.tx.getTransactionID();
+
413 auto flags = ctx_.app.getHashRouter().getFlags(id);
414
-
415 // Too late?
-
416 if ((*slep)[~sfCancelAfter] &&
- -
418 (*slep)[sfCancelAfter])
-
419 return tecNO_PERMISSION;
-
420 }
-
421
-
422 // Check cryptocondition fulfillment
-
423 {
-
424 auto const id = ctx_.tx.getTransactionID();
-
425 auto flags = ctx_.app.getHashRouter().getFlags(id);
+
415 auto const cb = ctx_.tx[~sfCondition];
+
416
+
417 // It's unlikely that the results of the check will
+
418 // expire from the hash router, but if it happens,
+
419 // simply re-run the check.
+
420 if (cb && !(flags & (SF_CF_INVALID | SF_CF_VALID)))
+
421 {
+
422 auto const fb = ctx_.tx[~sfFulfillment];
+
423
+
424 if (!fb)
+
425 return tecINTERNAL;
426
-
427 auto const cb = ctx_.tx[~sfCondition];
-
428
-
429 // It's unlikely that the results of the check will
-
430 // expire from the hash router, but if it happens,
-
431 // simply re-run the check.
-
432 if (cb && !(flags & (SF_CF_INVALID | SF_CF_VALID)))
-
433 {
-
434 auto const fb = ctx_.tx[~sfFulfillment];
-
435
-
436 if (!fb)
-
437 return tecINTERNAL;
-
438
-
439 if (checkCondition(*fb, *cb))
-
440 flags = SF_CF_VALID;
-
441 else
-
442 flags = SF_CF_INVALID;
-
443
-
444 ctx_.app.getHashRouter().setFlags(id, flags);
-
445 }
-
446
-
447 // If the check failed, then simply return an error
-
448 // and don't look at anything else.
-
449 if (flags & SF_CF_INVALID)
- -
451
-
452 // Check against condition in the ledger entry:
-
453 auto const cond = (*slep)[~sfCondition];
-
454
-
455 // If a condition wasn't specified during creation,
-
456 // one shouldn't be included now.
-
457 if (!cond && cb)
- -
459
-
460 // If a condition was specified during creation of
-
461 // the suspended payment, the identical condition
-
462 // must be presented again. We don't check if the
-
463 // fulfillment matches the condition since we did
-
464 // that in preflight.
-
465 if (cond && (cond != cb))
- -
467 }
-
468
-
469 // NOTE: Escrow payments cannot be used to fund accounts.
-
470 AccountID const destID = (*slep)[sfDestination];
-
471 auto const sled = ctx_.view().peek(keylet::account(destID));
-
472 if (!sled)
-
473 return tecNO_DST;
-
474
-
475 if (ctx_.view().rules().enabled(featureDepositAuth))
-
476 {
-
477 if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
-
478 !isTesSuccess(err))
-
479 return err;
-
480 }
-
481
-
482 AccountID const account = (*slep)[sfAccount];
-
483
-
484 // Remove escrow from owner directory
+
427 if (checkCondition(*fb, *cb))
+
428 flags = SF_CF_VALID;
+
429 else
+
430 flags = SF_CF_INVALID;
+
431
+
432 ctx_.app.getHashRouter().setFlags(id, flags);
+
433 }
+
434
+
435 // If the check failed, then simply return an error
+
436 // and don't look at anything else.
+
437 if (flags & SF_CF_INVALID)
+ +
439
+
440 // Check against condition in the ledger entry:
+
441 auto const cond = (*slep)[~sfCondition];
+
442
+
443 // If a condition wasn't specified during creation,
+
444 // one shouldn't be included now.
+
445 if (!cond && cb)
+ +
447
+
448 // If a condition was specified during creation of
+
449 // the suspended payment, the identical condition
+
450 // must be presented again. We don't check if the
+
451 // fulfillment matches the condition since we did
+
452 // that in preflight.
+
453 if (cond && (cond != cb))
+ +
455 }
+
456
+
457 // NOTE: Escrow payments cannot be used to fund accounts.
+
458 AccountID const destID = (*slep)[sfDestination];
+
459 auto const sled = ctx_.view().peek(keylet::account(destID));
+
460 if (!sled)
+
461 return tecNO_DST;
+
462
+
463 if (ctx_.view().rules().enabled(featureDepositAuth))
+
464 {
+
465 if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
+
466 !isTesSuccess(err))
+
467 return err;
+
468 }
+
469
+
470 AccountID const account = (*slep)[sfAccount];
+
471
+
472 // Remove escrow from owner directory
+
473 {
+
474 auto const page = (*slep)[sfOwnerNode];
+
475 if (!ctx_.view().dirRemove(
+
476 keylet::ownerDir(account), page, k.key, true))
+
477 {
+
478 JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
+
479 return tefBAD_LEDGER;
+
480 }
+
481 }
+
482
+
483 // Remove escrow from recipient's owner directory, if present.
+
484 if (auto const optPage = (*slep)[~sfDestinationNode])
485 {
-
486 auto const page = (*slep)[sfOwnerNode];
-
487 if (!ctx_.view().dirRemove(
-
488 keylet::ownerDir(account), page, k.key, true))
-
489 {
-
490 JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
-
491 return tefBAD_LEDGER;
-
492 }
-
493 }
-
494
-
495 // Remove escrow from recipient's owner directory, if present.
-
496 if (auto const optPage = (*slep)[~sfDestinationNode])
-
497 {
-
498 if (!ctx_.view().dirRemove(
-
499 keylet::ownerDir(destID), *optPage, k.key, true))
-
500 {
-
501 JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
-
502 return tefBAD_LEDGER;
-
503 }
-
504 }
+
486 if (!ctx_.view().dirRemove(
+
487 keylet::ownerDir(destID), *optPage, k.key, true))
+
488 {
+
489 JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
+
490 return tefBAD_LEDGER;
+
491 }
+
492 }
+
493
+
494 // Transfer amount to destination
+
495 (*sled)[sfBalance] = (*sled)[sfBalance] + (*slep)[sfAmount];
+
496 ctx_.view().update(sled);
+
497
+
498 // Adjust source owner count
+
499 auto const sle = ctx_.view().peek(keylet::account(account));
+ +
501 ctx_.view().update(sle);
+
502
+
503 // Remove escrow from ledger
+
504 ctx_.view().erase(slep);
505
-
506 // Transfer amount to destination
-
507 (*sled)[sfBalance] = (*sled)[sfBalance] + (*slep)[sfAmount];
-
508 ctx_.view().update(sled);
-
509
-
510 // Adjust source owner count
-
511 auto const sle = ctx_.view().peek(keylet::account(account));
- -
513 ctx_.view().update(sle);
-
514
-
515 // Remove escrow from ledger
-
516 ctx_.view().erase(slep);
-
517
-
518 return tesSUCCESS;
-
519}
-
520
-
521//------------------------------------------------------------------------------
+
506 return tesSUCCESS;
+
507}
+
508
+
509//------------------------------------------------------------------------------
+
510
+
511NotTEC
+ +
513{
+
514 if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
+
515 return temINVALID_FLAG;
+
516
+
517 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
+
518 return ret;
+
519
+
520 return preflight2(ctx);
+
521}
522
-
523NotTEC
- +
523TER
+
525{
-
526 if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
-
527 return temINVALID_FLAG;
-
528
-
529 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
-
530 return ret;
-
531
-
532 return preflight2(ctx);
-
533}
+
526 auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
+
527 auto const slep = ctx_.view().peek(k);
+
528 if (!slep)
+
529 return tecNO_TARGET;
+
530
+
531 if (ctx_.view().rules().enabled(fix1571))
+
532 {
+
533 auto const now = ctx_.view().info().parentCloseTime;
534
-
535TER
- -
537{
-
538 auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
-
539 auto const slep = ctx_.view().peek(k);
-
540 if (!slep)
-
541 return tecNO_TARGET;
-
542
-
543 if (ctx_.view().rules().enabled(fix1571))
+
535 // No cancel time specified: can't execute at all.
+
536 if (!(*slep)[~sfCancelAfter])
+
537 return tecNO_PERMISSION;
+
538
+
539 // Too soon: can't execute before the cancel time.
+
540 if (!after(now, (*slep)[sfCancelAfter]))
+
541 return tecNO_PERMISSION;
+
542 }
+
543 else
544 {
-
545 auto const now = ctx_.view().info().parentCloseTime;
-
546
-
547 // No cancel time specified: can't execute at all.
-
548 if (!(*slep)[~sfCancelAfter])
+
545 // Too soon?
+
546 if (!(*slep)[~sfCancelAfter] ||
+ +
548 (*slep)[sfCancelAfter])
549 return tecNO_PERMISSION;
-
550
-
551 // Too soon: can't execute before the cancel time.
-
552 if (!after(now, (*slep)[sfCancelAfter]))
-
553 return tecNO_PERMISSION;
-
554 }
-
555 else
-
556 {
-
557 // Too soon?
-
558 if (!(*slep)[~sfCancelAfter] ||
- -
560 (*slep)[sfCancelAfter])
-
561 return tecNO_PERMISSION;
-
562 }
-
563
-
564 AccountID const account = (*slep)[sfAccount];
-
565
-
566 // Remove escrow from owner directory
+
550 }
+
551
+
552 AccountID const account = (*slep)[sfAccount];
+
553
+
554 // Remove escrow from owner directory
+
555 {
+
556 auto const page = (*slep)[sfOwnerNode];
+
557 if (!ctx_.view().dirRemove(
+
558 keylet::ownerDir(account), page, k.key, true))
+
559 {
+
560 JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
+
561 return tefBAD_LEDGER;
+
562 }
+
563 }
+
564
+
565 // Remove escrow from recipient's owner directory, if present.
+
566 if (auto const optPage = (*slep)[~sfDestinationNode]; optPage)
567 {
-
568 auto const page = (*slep)[sfOwnerNode];
-
569 if (!ctx_.view().dirRemove(
-
570 keylet::ownerDir(account), page, k.key, true))
-
571 {
-
572 JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
-
573 return tefBAD_LEDGER;
-
574 }
-
575 }
-
576
-
577 // Remove escrow from recipient's owner directory, if present.
-
578 if (auto const optPage = (*slep)[~sfDestinationNode]; optPage)
-
579 {
-
580 if (!ctx_.view().dirRemove(
-
581 keylet::ownerDir((*slep)[sfDestination]),
-
582 *optPage,
-
583 k.key,
-
584 true))
-
585 {
-
586 JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
-
587 return tefBAD_LEDGER;
-
588 }
-
589 }
+
568 if (!ctx_.view().dirRemove(
+
569 keylet::ownerDir((*slep)[sfDestination]),
+
570 *optPage,
+
571 k.key,
+
572 true))
+
573 {
+
574 JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
+
575 return tefBAD_LEDGER;
+
576 }
+
577 }
+
578
+
579 // Transfer amount back to owner, decrement owner count
+
580 auto const sle = ctx_.view().peek(keylet::account(account));
+
581 (*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount];
+ +
583 ctx_.view().update(sle);
+
584
+
585 // Remove escrow from ledger
+
586 ctx_.view().erase(slep);
+
587
+
588 return tesSUCCESS;
+
589}
590
-
591 // Transfer amount back to owner, decrement owner count
-
592 auto const sle = ctx_.view().peek(keylet::account(account));
-
593 (*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount];
- -
595 ctx_.view().update(sle);
-
596
-
597 // Remove escrow from ledger
-
598 ctx_.view().erase(slep);
-
599
-
600 return tesSUCCESS;
-
601}
-
602
-
603} // namespace ripple
+
591} // namespace ripple
Stream fatal() const
Definition: Journal.h:352
Stream debug() const
Definition: Journal.h:328
virtual HashRouter & getHashRouter()=0
@@ -686,16 +680,16 @@ $(function() {
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition: ApplyView.h:315
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
-
TER doApply() override
Definition: Escrow.cpp:536
-
static NotTEC preflight(PreflightContext const &ctx)
Definition: Escrow.cpp:524
-
static NotTEC preflight(PreflightContext const &ctx)
Definition: Escrow.cpp:98
-
TER doApply() override
Definition: Escrow.cpp:170
-
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
Definition: Escrow.cpp:92
-
static TER preclaim(PreclaimContext const &ctx)
Definition: Escrow.cpp:158
-
static TER preclaim(PreclaimContext const &ctx)
Definition: Escrow.cpp:372
-
static NotTEC preflight(PreflightContext const &ctx)
Definition: Escrow.cpp:305
-
TER doApply() override
Definition: Escrow.cpp:385
-
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition: Escrow.cpp:359
+
TER doApply() override
Definition: Escrow.cpp:524
+
static NotTEC preflight(PreflightContext const &ctx)
Definition: Escrow.cpp:512
+
static NotTEC preflight(PreflightContext const &ctx)
Definition: Escrow.cpp:86
+
TER doApply() override
Definition: Escrow.cpp:158
+
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
Definition: Escrow.cpp:80
+
static TER preclaim(PreclaimContext const &ctx)
Definition: Escrow.cpp:146
+
static TER preclaim(PreclaimContext const &ctx)
Definition: Escrow.cpp:360
+
static NotTEC preflight(PreflightContext const &ctx)
Definition: Escrow.cpp:293
+
TER doApply() override
Definition: Escrow.cpp:373
+
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition: Escrow.cpp:347
int getFlags(uint256 const &key)
Definition: HashRouter.cpp:94
bool setFlags(uint256 const &key, int flags)
Set the flags on a hash.
Definition: HashRouter.cpp:102
A view into a ledger.
Definition: ReadView.h:52
@@ -724,7 +718,6 @@ $(function() { -
T message(T... args)
NotTEC checkFields(PreflightContext const &ctx)
TER valid(PreclaimContext const &ctx, AccountID const &src)
@@ -743,7 +736,7 @@ $(function() {
@ tefINTERNAL
Definition: TER.h:173
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:186
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:134
-
static bool checkCondition(Slice f, Slice c)
Definition: Escrow.cpp:287
+
static bool checkCondition(Slice f, Slice c)
Definition: Escrow.cpp:275
@ tecCRYPTOCONDITION_ERROR
Definition: TER.h:299
@ tecNO_DST
Definition: TER.h:277
@ tecUNFUNDED
Definition: TER.h:282
@@ -755,8 +748,8 @@ $(function() {
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:294
@ tesSUCCESS
Definition: TER.h:242
TER verifyDepositPreauth(ApplyContext &ctx, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE > const &sleDst)
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: View.cpp:2127
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:62
-
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: Escrow.cpp:86
@ temBAD_AMOUNT
Definition: TER.h:89
@ temMALFORMED
Definition: TER.h:87
@ temBAD_EXPIRATION
Definition: TER.h:91
@@ -775,7 +768,6 @@ $(function() {
beast::Journal const j
Definition: Transactor.h:39
-
T time_since_epoch(T... args)
diff --git a/Escrow_8h_source.html b/Escrow_8h_source.html index 0ca41816d9..f26c42014e 100644 --- a/Escrow_8h_source.html +++ b/Escrow_8h_source.html @@ -173,21 +173,21 @@ $(function() {
Definition: Escrow.h:76
EscrowCancel(ApplyContext &ctx)
Definition: Escrow.h:80
static constexpr ConsequencesFactoryType ConsequencesFactory
Definition: Escrow.h:78
-
TER doApply() override
Definition: Escrow.cpp:536
-
static NotTEC preflight(PreflightContext const &ctx)
Definition: Escrow.cpp:524
+
TER doApply() override
Definition: Escrow.cpp:524
+
static NotTEC preflight(PreflightContext const &ctx)
Definition: Escrow.cpp:512
Definition: Escrow.h:28
EscrowCreate(ApplyContext &ctx)
Definition: Escrow.h:32
-
static NotTEC preflight(PreflightContext const &ctx)
Definition: Escrow.cpp:98
-
TER doApply() override
Definition: Escrow.cpp:170
-
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
Definition: Escrow.cpp:92
+
static NotTEC preflight(PreflightContext const &ctx)
Definition: Escrow.cpp:86
+
TER doApply() override
Definition: Escrow.cpp:158
+
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
Definition: Escrow.cpp:80
static constexpr ConsequencesFactoryType ConsequencesFactory
Definition: Escrow.h:30
-
static TER preclaim(PreclaimContext const &ctx)
Definition: Escrow.cpp:158
+
static TER preclaim(PreclaimContext const &ctx)
Definition: Escrow.cpp:146
Definition: Escrow.h:52
-
static TER preclaim(PreclaimContext const &ctx)
Definition: Escrow.cpp:372
-
static NotTEC preflight(PreflightContext const &ctx)
Definition: Escrow.cpp:305
-
TER doApply() override
Definition: Escrow.cpp:385
+
static TER preclaim(PreclaimContext const &ctx)
Definition: Escrow.cpp:360
+
static NotTEC preflight(PreflightContext const &ctx)
Definition: Escrow.cpp:293
+
TER doApply() override
Definition: Escrow.cpp:373
EscrowFinish(ApplyContext &ctx)
Definition: Escrow.h:56
-
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition: Escrow.cpp:359
+
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition: Escrow.cpp:347
static constexpr ConsequencesFactoryType ConsequencesFactory
Definition: Escrow.h:54
A view into a ledger.
Definition: ReadView.h:52
Definition: STTx.h:48
diff --git a/Escrow__test_8cpp_source.html b/Escrow__test_8cpp_source.html index 0590ef7c91..ae85f32ad0 100644 --- a/Escrow__test_8cpp_source.html +++ b/Escrow__test_8cpp_source.html @@ -1798,12 +1798,12 @@ $(function() {
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:120
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:212
-
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:532
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:534
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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:493
-
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:281
-
Application & app()
Definition: Env.h:258
+
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:495
+
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:283
+
Application & app()
Definition: Env.h:260
beast::Journal const journal
Definition: Env.h:161
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
@@ -1823,9 +1823,9 @@ $(function() {
T memcpy(T... args)
Keylet escrow(AccountID const &src, std::uint32_t seq) noexcept
An escrow entry.
Definition: Indexes.cpp:380
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:365
-
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:32
-
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:50
-
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:83
+
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:32
+
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:50
+
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:83
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition: deposit.cpp:32
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition: deposit.cpp:54
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition: ticket.cpp:31
diff --git a/FixNFTokenPageLinks__test_8cpp_source.html b/FixNFTokenPageLinks__test_8cpp_source.html index 83ce6d85ac..158f1803bc 100644 --- a/FixNFTokenPageLinks__test_8cpp_source.html +++ b/FixNFTokenPageLinks__test_8cpp_source.html @@ -764,9 +764,9 @@ $(function() {
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
A transaction testing environment.
Definition: Env.h:120
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:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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:767
+
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:769
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/Flow__test_8cpp_source.html b/Flow__test_8cpp_source.html index ffa4225ffe..004bad4965 100644 --- a/Flow__test_8cpp_source.html +++ b/Flow__test_8cpp_source.html @@ -1612,11 +1612,11 @@ $(function() {
AccountID id() const
Returns the Account ID.
Definition: Account.h:107
A transaction testing environment.
Definition: Env.h:120
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:212
-
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:532
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:534
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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:258
+
Application & app()
Definition: Env.h:260
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
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition: Env.cpp:221
diff --git a/FullBelowCache_8h_source.html b/FullBelowCache_8h_source.html index d8ca19451a..9ff4796426 100644 --- a/FullBelowCache_8h_source.html +++ b/FullBelowCache_8h_source.html @@ -117,7 +117,7 @@ $(function() {
42 using CacheType = KeyCache;
43
44public:
-
45 enum { defaultCacheTargetSize = 0 };
+
45 enum { defaultCacheTargetSize = 0 };
46
47 using key_type = uint256;
48 using clock_type = typename CacheType::clock_type;
@@ -128,7 +128,7 @@ $(function() {
60 beast::Journal j,
61 beast::insight::Collector::ptr const& collector =
62 beast::insight::NullCollector::New(),
-
63 std::size_t target_size = defaultCacheTargetSize,
+
63 std::size_t target_size = defaultCacheTargetSize,
64 std::chrono::seconds expiration = std::chrono::minutes{2})
65 : m_cache(name, target_size, expiration, clock, j, collector), m_gen(1)
66 {
@@ -210,13 +210,13 @@ $(function() {
void sweep()
Remembers which tree keys have all descendants resident.
+
@ defaultCacheTargetSize
void reset()
std::atomic< std::uint32_t > m_gen
void insert(key_type const &key)
Insert a key into the cache.
void sweep()
Remove expired cache items.
BasicFullBelowCache(std::string const &name, clock_type &clock, beast::Journal j, beast::insight::Collector::ptr const &collector=beast::insight::NullCollector::New(), std::size_t target_size=defaultCacheTargetSize, std::chrono::seconds expiration=std::chrono::minutes{2})
Construct the cache.
typename CacheType::clock_type clock_type
-
@ defaultCacheTargetSize
clock_type & clock()
Return the clock associated with the cache.
std::uint32_t getGeneration(void) const
generation determines whether cached entry is valid
bool touch_if_exists(key_type const &key)
Refresh the last access time of an item, if it exists.
diff --git a/GRPCServer_8cpp_source.html b/GRPCServer_8cpp_source.html index 0d6872dc3b..eff49b1b89 100644 --- a/GRPCServer_8cpp_source.html +++ b/GRPCServer_8cpp_source.html @@ -793,7 +793,7 @@ $(function() {
Condition
Definition: Handler.h:38
@ NO_CONDITION
Definition: Handler.h:39
error_code_i conditionMet(Condition condition_required, T &context)
Definition: Handler.h:80
-
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:177
+
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:178
Charge const feeMediumBurdenRPC
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
@ forward
diff --git a/GatewayBalances__test_8cpp_source.html b/GatewayBalances__test_8cpp_source.html index 10d28f79cd..6a23415762 100644 --- a/GatewayBalances__test_8cpp_source.html +++ b/GatewayBalances__test_8cpp_source.html @@ -361,7 +361,7 @@ $(function() {
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:120
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:258
+
Application & app()
Definition: Env.h:260
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition: trust.cpp:32
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
diff --git a/GetAggregatePrice__test_8cpp_source.html b/GetAggregatePrice__test_8cpp_source.html index 7a3a4e5fd3..eed5b5a3bf 100644 --- a/GetAggregatePrice__test_8cpp_source.html +++ b/GetAggregatePrice__test_8cpp_source.html @@ -435,7 +435,7 @@ $(function() {
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:120
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
void testErrors()
diff --git a/Handler_8cpp_source.html b/Handler_8cpp_source.html index ec0615795d..b4462d83e2 100644 --- a/Handler_8cpp_source.html +++ b/Handler_8cpp_source.html @@ -210,7 +210,7 @@ $(function() {
133 {"ledger_request", byRef(&doLedgerRequest), Role::ADMIN, NO_CONDITION},
134 {"log_level", byRef(&doLogLevel), Role::ADMIN, NO_CONDITION},
135 {"logrotate", byRef(&doLogRotate), Role::ADMIN, NO_CONDITION},
-
136 {"manifest", byRef(&doManifest), Role::USER, NO_CONDITION},
+
136 {"manifest", byRef(&doManifest), Role::USER, NO_CONDITION},
137 {"nft_buy_offers", byRef(&doNFTBuyOffers), Role::USER, NO_CONDITION},
138 {"nft_sell_offers", byRef(&doNFTSellOffers), Role::USER, NO_CONDITION},
139 {"noripple_check", byRef(&doNoRippleCheck), Role::USER, NO_CONDITION},
@@ -444,7 +444,7 @@ $(function() {
Json::Value doSign(RPC::JsonContext &)
Definition: SignHandler.cpp:34
Json::Value doAccountTxJson(RPC::JsonContext &context)
Definition: AccountTx.cpp:404
Json::Value doAccountLines(RPC::JsonContext &context)
-
Json::Value doTxJson(RPC::JsonContext &)
Definition: Tx.cpp:282
+
Json::Value doTxJson(RPC::JsonContext &)
Definition: Tx.cpp:286
Json::Value doAMMInfo(RPC::JsonContext &context)
Definition: AMMInfo.cpp:76
Json::Value doPeerReservationsList(RPC::JsonContext &)
Json::Value doServerInfo(RPC::JsonContext &)
Definition: ServerInfo.cpp:322
@@ -464,8 +464,8 @@ $(function() {
Json::Value doFetchInfo(RPC::JsonContext &context)
Definition: FetchInfo.cpp:30
Json::Value doLedgerHeader(RPC::JsonContext &)
Json::Value doRandom(RPC::JsonContext &)
Definition: Random.cpp:39
-
Json::Value doManifest(RPC::JsonContext &)
Json::Value doGetCounts(RPC::JsonContext &context)
Definition: GetCounts.cpp:138
+
Json::Value doManifest(RPC::JsonContext &context)
Definition: DoManifest.cpp:30
Json::Value doPing(RPC::JsonContext &)
Definition: Ping.cpp:33
Json::Value doBlackList(RPC::JsonContext &context)
Definition: BlackList.cpp:29
Json::Value doLedgerData(RPC::JsonContext &)
Definition: LedgerData.cpp:46
diff --git a/Handlers_8h_source.html b/Handlers_8h_source.html index e0251c2111..72ba3f4b9d 100644 --- a/Handlers_8h_source.html +++ b/Handlers_8h_source.html @@ -171,7 +171,7 @@ $(function() {
93Json::Value
94doLogRotate(RPC::JsonContext&);
95Json::Value
-
96doManifest(RPC::JsonContext&);
+
96doManifest(RPC::JsonContext&);
97Json::Value
98doNFTBuyOffers(RPC::JsonContext&);
99Json::Value
@@ -273,7 +273,7 @@ $(function() {
Json::Value doSign(RPC::JsonContext &)
Definition: SignHandler.cpp:34
Json::Value doAccountTxJson(RPC::JsonContext &context)
Definition: AccountTx.cpp:404
Json::Value doAccountLines(RPC::JsonContext &context)
-
Json::Value doTxJson(RPC::JsonContext &)
Definition: Tx.cpp:282
+
Json::Value doTxJson(RPC::JsonContext &)
Definition: Tx.cpp:286
Json::Value doAMMInfo(RPC::JsonContext &context)
Definition: AMMInfo.cpp:76
Json::Value doPeerReservationsList(RPC::JsonContext &)
Json::Value doServerInfo(RPC::JsonContext &)
Definition: ServerInfo.cpp:322
@@ -294,8 +294,8 @@ $(function() {
Json::Value doLedgerHeader(RPC::JsonContext &)
Json::Value doPause(RPC::JsonContext &)
Json::Value doRandom(RPC::JsonContext &)
Definition: Random.cpp:39
-
Json::Value doManifest(RPC::JsonContext &)
Json::Value doGetCounts(RPC::JsonContext &context)
Definition: GetCounts.cpp:138
+
Json::Value doManifest(RPC::JsonContext &context)
Definition: DoManifest.cpp:30
Json::Value doPing(RPC::JsonContext &)
Definition: Ping.cpp:33
Json::Value doBlackList(RPC::JsonContext &context)
Definition: BlackList.cpp:29
Json::Value doLedgerData(RPC::JsonContext &)
Definition: LedgerData.cpp:46
diff --git a/InnerObjectFormats__test_8cpp_source.html b/InnerObjectFormats__test_8cpp_source.html index 7db433e720..61b755222d 100644 --- a/InnerObjectFormats__test_8cpp_source.html +++ b/InnerObjectFormats__test_8cpp_source.html @@ -295,7 +295,7 @@ $(function() {
std::optional< STObject > object
The STObject if the parse was successful.
Definition: STParsedJSON.h:51
A transaction testing environment.
Definition: Env.h:120
static TestJSONTxt const testArray[]
-
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:201
+
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:202
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
bool const expectFail
diff --git a/InvariantCheck_8cpp_source.html b/InvariantCheck_8cpp_source.html index b9a7eb0906..d184bd096c 100644 --- a/InvariantCheck_8cpp_source.html +++ b/InvariantCheck_8cpp_source.html @@ -164,7 +164,7 @@ $(function() {
86XRPNotCreated::visitEntry(
87 bool isDelete,
88 std::shared_ptr<SLE const> const& before,
-
89 std::shared_ptr<SLE const> const& after)
+
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
@@ -192,9 +192,9 @@ $(function() {
114 }
115 }
116
-
117 if (after)
+
117 if (after)
118 {
-
119 switch (after->getType())
+
119 switch (after->getType())
120 {
121 case ltACCOUNT_ROOT:
122 drops_ += (*after)[sfBalance].xrp().drops();
@@ -249,7 +249,7 @@ $(function() {
171XRPBalanceChecks::visitEntry(
172 bool,
173 std::shared_ptr<SLE const> const& before,
-
174 std::shared_ptr<SLE const> const& after)
+
174 std::shared_ptr<SLE const> const& after)
175{
176 auto isBad = [](STAmount const& balance) {
177 if (!balance.native())
@@ -272,8 +272,8 @@ $(function() {
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]);
+
197 if (after && after->getType() == ltACCOUNT_ROOT)
+
198 bad_ |= isBad((*after)[sfBalance]);
199}
200
201bool
@@ -299,7 +299,7 @@ $(function() {
221NoBadOffers::visitEntry(
222 bool isDelete,
223 std::shared_ptr<SLE const> const& before,
-
224 std::shared_ptr<SLE const> const& after)
+
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
@@ -316,8 +316,8 @@ $(function() {
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]);
+
241 if (after && after->getType() == ltOFFER)
+
242 bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]);
243}
244
245bool
@@ -343,7 +343,7 @@ $(function() {
265NoZeroEscrow::visitEntry(
266 bool isDelete,
267 std::shared_ptr<SLE const> const& before,
-
268 std::shared_ptr<SLE const> const& after)
+
268 std::shared_ptr<SLE const> const& after)
269{
270 auto isBad = [](STAmount const& amount) {
271 if (!amount.native())
@@ -361,8 +361,8 @@ $(function() {
283 if (before && before->getType() == ltESCROW)
284 bad_ |= isBad((*before)[sfAmount]);
285
-
286 if (after && after->getType() == ltESCROW)
-
287 bad_ |= isBad((*after)[sfAmount]);
+
286 if (after && after->getType() == ltESCROW)
+
287 bad_ |= isBad((*after)[sfAmount]);
288}
289
290bool
@@ -531,14 +531,14 @@ $(function() {
453LedgerEntryTypesMatch::visitEntry(
454 bool,
455 std::shared_ptr<SLE const> const& before,
-
456 std::shared_ptr<SLE const> const& after)
+
456 std::shared_ptr<SLE const> const& after)
457{
-
458 if (before && after && before->getType() != after->getType())
+
458 if (before && after && before->getType() != after->getType())
459 typeMismatch_ = true;
460
-
461 if (after)
+
461 if (after)
462 {
-
463 switch (after->getType())
+
463 switch (after->getType())
464 {
465 case ltACCOUNT_ROOT:
466 case ltDIR_NODE:
@@ -604,16 +604,16 @@ $(function() {
526NoXRPTrustLines::visitEntry(
527 bool,
528 std::shared_ptr<SLE const> const&,
-
529 std::shared_ptr<SLE const> const& after)
+
529 std::shared_ptr<SLE const> const& after)
530{
-
531 if (after && after->getType() == ltRIPPLE_STATE)
+
531 if (after && after->getType() == ltRIPPLE_STATE)
532 {
533 // checking the issue directly here instead of
534 // relying on .native() just in case native somehow
535 // were systematically incorrect
536 xrpTrustLine_ =
-
537 after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
-
538 after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
+
537 after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
+
538 after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
539 }
540}
541
@@ -638,11 +638,11 @@ $(function() {
560NoDeepFreezeTrustLinesWithoutFreeze::visitEntry(
561 bool,
562 std::shared_ptr<SLE const> const&,
-
563 std::shared_ptr<SLE const> const& after)
+
563 std::shared_ptr<SLE const> const& after)
564{
-
565 if (after && after->getType() == ltRIPPLE_STATE)
+
565 if (after && after->getType() == ltRIPPLE_STATE)
566 {
-
567 std::uint32_t const uFlags = after->getFieldU32(sfFlags);
+
567 std::uint32_t const uFlags = after->getFieldU32(sfFlags);
568 bool const lowFreeze = uFlags & lsfLowFreeze;
569 bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze;
570
@@ -676,7 +676,7 @@ $(function() {
598TransfersNotFrozen::visitEntry(
599 bool isDelete,
600 std::shared_ptr<SLE const> const& before,
-
601 std::shared_ptr<SLE const> const& after)
+
601 std::shared_ptr<SLE const> const& after)
602{
603 /*
604 * A trust line freeze state alone doesn't determine if a transfer is
@@ -688,18 +688,18 @@ $(function() {
610 * Only in validateIssuerChanges, after we collected all changes can we
611 * determine if the transfer is valid.
612 */
-
613 if (!isValidEntry(before, after))
+
613 if (!isValidEntry(before, after))
614 {
615 return;
616 }
617
-
618 auto const balanceChange = calculateBalanceChange(before, after, isDelete);
+
618 auto const balanceChange = calculateBalanceChange(before, after, isDelete);
619 if (balanceChange.signum() == 0)
620 {
621 return;
622 }
623
-
624 recordBalanceChanges(after, balanceChange);
+
624 recordBalanceChanges(after, balanceChange);
625}
626
627bool
@@ -759,19 +759,19 @@ $(function() {
681bool
682TransfersNotFrozen::isValidEntry(
683 std::shared_ptr<SLE const> const& before,
-
684 std::shared_ptr<SLE const> const& after)
+
684 std::shared_ptr<SLE const> const& after)
685{
686 // `after` can never be null, even if the trust line is deleted.
687 XRPL_ASSERT(
-
688 after, "ripple::TransfersNotFrozen::isValidEntry : valid after.");
-
689 if (!after)
+
688 after, "ripple::TransfersNotFrozen::isValidEntry : valid after.");
+
689 if (!after)
690 {
691 return false;
692 }
693
-
694 if (after->getType() == ltACCOUNT_ROOT)
+
694 if (after->getType() == ltACCOUNT_ROOT)
695 {
-
696 possibleIssuers_.emplace(after->at(sfAccount), after);
+
696 possibleIssuers_.emplace(after->at(sfAccount), after);
697 return false;
698 }
699
@@ -781,14 +781,14 @@ $(function() {
703 * This type check is still necessary here because it prevents potential
704 * issues in subsequent processing.
705 */
-
706 return after->getType() == ltRIPPLE_STATE &&
+
706 return after->getType() == ltRIPPLE_STATE &&
707 (!before || before->getType() == ltRIPPLE_STATE);
708}
709
710STAmount
711TransfersNotFrozen::calculateBalanceChange(
712 std::shared_ptr<SLE const> const& before,
-
713 std::shared_ptr<SLE const> const& after,
+
713 std::shared_ptr<SLE const> const& after,
714 bool isDelete)
715{
716 auto const getBalance = [](auto const& line, auto const& other, bool zero) {
@@ -802,14 +802,14 @@ $(function() {
724 * created frozen, but the sender might be, so the starting balance must be
725 * treated as zero.
726 */
-
727 auto const balanceBefore = getBalance(before, after, false);
+
727 auto const balanceBefore = getBalance(before, after, false);
728
729 /* Same as above, trust lines can be dynamically deleted, and for frozen
730 * trust lines, payments not involving the issuer must be blocked. This is
731 * achieved by treating the final balance as zero when isDelete=true to
732 * ensure frozen line restrictions are enforced even during deletion.
733 */
-
734 auto const balanceAfter = getBalance(after, before, isDelete);
+
734 auto const balanceAfter = getBalance(after, before, isDelete);
735
736 return balanceAfter - balanceBefore;
737}
@@ -830,21 +830,21 @@ $(function() {
752
753void
754TransfersNotFrozen::recordBalanceChanges(
-
755 std::shared_ptr<SLE const> const& after,
+
755 std::shared_ptr<SLE const> const& after,
756 STAmount const& balanceChange)
757{
758 auto const balanceChangeSign = balanceChange.signum();
-
759 auto const currency = after->at(sfBalance).getCurrency();
+
759 auto const currency = after->at(sfBalance).getCurrency();
760
761 // Change from low account's perspective, which is trust line default
762 recordBalance(
-
763 {currency, after->at(sfHighLimit).getIssuer()},
-
764 {after, balanceChangeSign});
+
763 {currency, after->at(sfHighLimit).getIssuer()},
+
764 {after, balanceChangeSign});
765
766 // Change from high account's perspective, which reverses the sign.
767 recordBalance(
-
768 {currency, after->at(sfLowLimit).getIssuer()},
-
769 {after, -balanceChangeSign});
+
768 {currency, after->at(sfLowLimit).getIssuer()},
+
769 {after, -balanceChangeSign});
770}
771
772std::shared_ptr<SLE const>
@@ -954,9 +954,9 @@ $(function() {
876ValidNewAccountRoot::visitEntry(
877 bool,
878 std::shared_ptr<SLE const> const& before,
-
879 std::shared_ptr<SLE const> const& after)
+
879 std::shared_ptr<SLE const> const& after)
880{
-
881 if (!before && after->getType() == ltACCOUNT_ROOT)
+
881 if (!before && after->getType() == ltACCOUNT_ROOT)
882 {
883 accountsCreated_++;
884 accountSeq_ = (*after)[sfSequence];
@@ -1011,13 +1011,13 @@ $(function() {
933ValidNFTokenPage::visitEntry(
934 bool isDelete,
935 std::shared_ptr<SLE const> const& before,
-
936 std::shared_ptr<SLE const> const& after)
+
936 std::shared_ptr<SLE const> const& after)
937{
938 static constexpr uint256 const& pageBits = nft::pageMask;
939 static constexpr uint256 const accountBits = ~pageBits;
940
941 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
-
942 (after && after->getType() != ltNFTOKEN_PAGE))
+
942 (after && after->getType() != ltNFTOKEN_PAGE))
943 return;
944
945 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
@@ -1095,10 +1095,10 @@ $(function() {
1017 }
1018 }
1019
-
1020 if (after)
-
1021 check(after);
+
1020 if (after)
+
1021 check(after);
1022
-
1023 if (!isDelete && before && after)
+
1023 if (!isDelete && before && after)
1024 {
1025 // If the NFTokenPage
1026 // 1. Has a NextMinPage field in before, but loses it in after, and
@@ -1107,7 +1107,7 @@ $(function() {
1029 // NFToken pages in the NFToken directory.
1030 if ((before->key() & nft::pageMask) != nft::pageMask &&
1031 before->isFieldPresent(sfNextPageMin) &&
-
1032 !after->isFieldPresent(sfNextPageMin))
+
1032 !after->isFieldPresent(sfNextPageMin))
1033 {
1034 deletedLink_ = true;
1035 }
@@ -1175,7 +1175,7 @@ $(function() {
1097NFTokenCountTracking::visitEntry(
1098 bool,
1099 std::shared_ptr<SLE const> const& before,
-
1100 std::shared_ptr<SLE const> const& after)
+
1100 std::shared_ptr<SLE const> const& after)
1101{
1102 if (before && before->getType() == ltACCOUNT_ROOT)
1103 {
@@ -1183,7 +1183,7 @@ $(function() {
1105 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
1106 }
1107
-
1108 if (after && after->getType() == ltACCOUNT_ROOT)
+
1108 if (after && after->getType() == ltACCOUNT_ROOT)
1109 {
1110 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
1111 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
@@ -1360,9 +1360,9 @@ $(function() {
1282ValidMPTIssuance::visitEntry(
1283 bool isDelete,
1284 std::shared_ptr<SLE const> const& before,
-
1285 std::shared_ptr<SLE const> const& after)
+
1285 std::shared_ptr<SLE const> const& after)
1286{
-
1287 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
+
1287 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
1288 {
1289 if (isDelete)
1290 mptIssuancesDeleted_++;
@@ -1370,7 +1370,7 @@ $(function() {
1292 mptIssuancesCreated_++;
1293 }
1294
-
1295 if (after && after->getType() == ltMPTOKEN)
+
1295 if (after && after->getType() == ltMPTOKEN)
1296 {
1297 if (isDelete)
1298 mptokensDeleted_++;
@@ -1526,11 +1526,11 @@ $(function() {
1448ValidPermissionedDomain::visitEntry(
1449 bool,
1450 std::shared_ptr<SLE const> const& before,
-
1451 std::shared_ptr<SLE const> const& after)
+
1451 std::shared_ptr<SLE const> const& after)
1452{
1453 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
1454 return;
-
1455 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
+
1455 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
1456 return;
1457
1458 auto check = [](SleStatus& sleStatus,
@@ -1560,13 +1560,13 @@ $(function() {
1482 if (before)
1483 {
1484 sleStatus_[0] = SleStatus();
-
1485 check(*sleStatus_[0], after);
+
1485 check(*sleStatus_[0], after);
1486 }
1487
-
1488 if (after)
+
1488 if (after)
1489 {
1490 sleStatus_[1] = SleStatus();
-
1491 check(*sleStatus_[1], after);
+
1491 check(*sleStatus_[1], after);
1492 }
1493}
1494
@@ -1751,7 +1751,7 @@ $(function() {
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition: Indexes.h:368
@ tesSUCCESS
Definition: TER.h:242
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:309
-
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: Escrow.cpp:86
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: View.cpp:2127
diff --git a/InvariantCheck_8h_source.html b/InvariantCheck_8h_source.html index d240dda34b..080d88cd12 100644 --- a/InvariantCheck_8h_source.html +++ b/InvariantCheck_8h_source.html @@ -121,7 +121,7 @@ $(function() {
58 visitEntry(
59 bool isDelete,
60 std::shared_ptr<SLE const> const& before,
-
61 std::shared_ptr<SLE const> const& after);
+
61 std::shared_ptr<SLE const> const& after);
62
75 bool
76 finalize(
@@ -330,12 +330,12 @@ $(function() {
341 bool
342 isValidEntry(
343 std::shared_ptr<SLE const> const& before,
-
344 std::shared_ptr<SLE const> const& after);
+
344 std::shared_ptr<SLE const> const& after);
345
346 STAmount
347 calculateBalanceChange(
348 std::shared_ptr<SLE const> const& before,
-
349 std::shared_ptr<SLE const> const& after,
+
349 std::shared_ptr<SLE const> const& after,
350 bool isDelete);
351
352 void
@@ -343,7 +343,7 @@ $(function() {
354
355 void
356 recordBalanceChanges(
-
357 std::shared_ptr<SLE const> const& after,
+
357 std::shared_ptr<SLE const> const& after,
358 STAmount const& balanceChange);
359
360 std::shared_ptr<SLE const>
@@ -683,8 +683,8 @@ $(function() {
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: View.cpp:2127
InvariantChecks getInvariantChecks()
get a tuple of all invariant checks
-
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: Escrow.cpp:86
diff --git a/JSONRPC__test_8cpp_source.html b/JSONRPC__test_8cpp_source.html index 56628ac799..cb332528a8 100644 --- a/JSONRPC__test_8cpp_source.html +++ b/JSONRPC__test_8cpp_source.html @@ -2832,15 +2832,15 @@ $(function() {
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:120
-
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
Account const & master
Definition: Env.h:124
-
Application & app()
Definition: Env.h:258
-
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:767
+
Application & app()
Definition: Env.h:260
+
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:769
@ uintValue
unsigned integer value
Definition: json_value.h:39
Json::Value transactionSign(Json::Value jvRequest, unsigned apiVersion, NetworkOPs::FailHard failType, Role role, std::chrono::seconds validatedLedgerAge, Application &app)
Returns a Json::objectValue.
std::function< void(std::shared_ptr< Transaction > &transaction, bool bUnlimited, bool bLocal, NetworkOPs::FailHard failType)> ProcessTransactionFn
-
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:201
+
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:202
Json::Value transactionSubmitMultiSigned(Json::Value jvRequest, unsigned apiVersion, NetworkOPs::FailHard failType, Role role, std::chrono::seconds validatedLedgerAge, Application &app, ProcessTransactionFn const &processTransaction)
Returns a Json::objectValue.
static constexpr std::integral_constant< unsigned, Version > apiVersion
Definition: ApiVersion.h:54
Json::Value transactionSubmit(Json::Value jvRequest, unsigned apiVersion, NetworkOPs::FailHard failType, Role role, std::chrono::seconds validatedLedgerAge, Application &app, ProcessTransactionFn const &processTransaction)
Returns a Json::objectValue.
diff --git a/KeyGeneration__test_8cpp_source.html b/KeyGeneration__test_8cpp_source.html index 51991caeb4..0271bca2f9 100644 --- a/KeyGeneration__test_8cpp_source.html +++ b/KeyGeneration__test_8cpp_source.html @@ -1005,7 +1005,7 @@ $(function() {
static char const * master_seed
static char const * passphrase
static key_strings const ed25519_strings
-
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:201
+
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:202
static key_strings const strong_brain_strings
static key_strings const secp256k1_strings
std::optional< std::pair< PublicKey, SecretKey > > keypairForSignature(Json::Value const &params, Json::Value &error, unsigned int apiVersion)
Definition: RPCHelpers.cpp:795
diff --git a/LedgerEntry_8cpp_source.html b/LedgerEntry_8cpp_source.html index 7bb0a9e212..c695b9b343 100644 --- a/LedgerEntry_8cpp_source.html +++ b/LedgerEntry_8cpp_source.html @@ -115,819 +115,819 @@ $(function() {
37
38namespace ripple {
39
-
40static STArray
-
41parseAuthorizeCredentials(Json::Value const& jv)
+
40static std::optional<uint256>
+
41parseIndex(Json::Value const& params, Json::Value& jvResult)
42{
-
43 STArray arr(sfAuthorizeCredentials, jv.size());
-
44 for (auto const& jo : jv)
+
43 uint256 uNodeIndex;
+
44 if (!uNodeIndex.parseHex(params.asString()))
45 {
-
46 if (!jo.isObject() || //
-
47 !jo.isMember(jss::issuer) || !jo[jss::issuer].isString() ||
-
48 !jo.isMember(jss::credential_type) ||
-
49 !jo[jss::credential_type].isString())
-
50 return {};
-
51
-
52 auto const issuer = parseBase58<AccountID>(jo[jss::issuer].asString());
-
53 if (!issuer || !*issuer)
-
54 return {};
-
55
-
56 auto const credentialType =
-
57 strUnHex(jo[jss::credential_type].asString());
-
58 if (!credentialType || credentialType->empty() ||
-
59 credentialType->size() > maxCredentialTypeLength)
-
60 return {};
-
61
-
62 auto credential = STObject::makeInnerObject(sfCredential);
-
63 credential.setAccountID(sfIssuer, *issuer);
-
64 credential.setFieldVL(sfCredentialType, *credentialType);
-
65 arr.push_back(std::move(credential));
-
66 }
-
67
-
68 return arr;
-
69}
-
70
-
71static std::optional<uint256>
-
72parseIndex(Json::Value const& params, Json::Value& jvResult)
-
73{
-
74 uint256 uNodeIndex;
-
75 if (!uNodeIndex.parseHex(params.asString()))
-
76 {
-
77 jvResult[jss::error] = "malformedRequest";
-
78 return std::nullopt;
-
79 }
-
80
-
81 return uNodeIndex;
-
82}
-
83
-
84static std::optional<uint256>
-
85parseAccountRoot(Json::Value const& params, Json::Value& jvResult)
-
86{
-
87 auto const account = parseBase58<AccountID>(params.asString());
-
88 if (!account || account->isZero())
-
89 {
-
90 jvResult[jss::error] = "malformedAddress";
-
91 return std::nullopt;
-
92 }
-
93
-
94 return keylet::account(*account).key;
-
95}
-
96
-
97static std::optional<uint256>
-
98parseCheck(Json::Value const& params, Json::Value& jvResult)
-
99{
-
100 uint256 uNodeIndex;
-
101 if (!uNodeIndex.parseHex(params.asString()))
-
102 {
-
103 jvResult[jss::error] = "malformedRequest";
-
104 return std::nullopt;
-
105 }
-
106
-
107 return uNodeIndex;
-
108}
+
46 jvResult[jss::error] = "malformedRequest";
+
47 return std::nullopt;
+
48 }
+
49
+
50 return uNodeIndex;
+
51}
+
52
+
53static std::optional<uint256>
+
54parseAccountRoot(Json::Value const& params, Json::Value& jvResult)
+
55{
+
56 auto const account = parseBase58<AccountID>(params.asString());
+
57 if (!account || account->isZero())
+
58 {
+
59 jvResult[jss::error] = "malformedAddress";
+
60 return std::nullopt;
+
61 }
+
62
+
63 return keylet::account(*account).key;
+
64}
+
65
+
66static std::optional<uint256>
+
67parseAMM(Json::Value const& params, Json::Value& jvResult)
+
68{
+
69 if (!params.isObject())
+
70 {
+
71 uint256 uNodeIndex;
+
72 if (!uNodeIndex.parseHex(params.asString()))
+
73 {
+
74 jvResult[jss::error] = "malformedRequest";
+
75 return std::nullopt;
+
76 }
+
77 return uNodeIndex;
+
78 }
+
79
+
80 if (!params.isMember(jss::asset) || !params.isMember(jss::asset2))
+
81 {
+
82 jvResult[jss::error] = "malformedRequest";
+
83 return std::nullopt;
+
84 }
+
85
+
86 try
+
87 {
+
88 auto const issue = issueFromJson(params[jss::asset]);
+
89 auto const issue2 = issueFromJson(params[jss::asset2]);
+
90 return keylet::amm(issue, issue2).key;
+
91 }
+
92 catch (std::runtime_error const&)
+
93 {
+
94 jvResult[jss::error] = "malformedRequest";
+
95 return std::nullopt;
+
96 }
+
97}
+
98
+
99static std::optional<uint256>
+
100parseBridge(Json::Value const& params, Json::Value& jvResult)
+
101{
+
102 // return the keylet for the specified bridge or nullopt if the
+
103 // request is malformed
+
104 auto const maybeKeylet = [&]() -> std::optional<Keylet> {
+
105 try
+
106 {
+
107 if (!params.isMember(jss::bridge_account))
+
108 return std::nullopt;
109
-
110static std::optional<uint256>
-
111parseDepositPreauth(Json::Value const& dp, Json::Value& jvResult)
-
112{
-
113 if (!dp.isObject())
-
114 {
-
115 uint256 uNodeIndex;
-
116 if (!dp.isString() || !uNodeIndex.parseHex(dp.asString()))
-
117 {
-
118 jvResult[jss::error] = "malformedRequest";
-
119 return std::nullopt;
-
120 }
-
121 return uNodeIndex;
-
122 }
-
123
-
124 // clang-format off
-
125 if (
-
126 (!dp.isMember(jss::owner) || !dp[jss::owner].isString()) ||
-
127 (dp.isMember(jss::authorized) == dp.isMember(jss::authorized_credentials)) ||
-
128 (dp.isMember(jss::authorized) && !dp[jss::authorized].isString()) ||
-
129 (dp.isMember(jss::authorized_credentials) && !dp[jss::authorized_credentials].isArray())
-
130 )
-
131 // clang-format on
-
132 {
-
133 jvResult[jss::error] = "malformedRequest";
-
134 return std::nullopt;
-
135 }
-
136
-
137 auto const owner = parseBase58<AccountID>(dp[jss::owner].asString());
-
138 if (!owner)
-
139 {
-
140 jvResult[jss::error] = "malformedOwner";
-
141 return std::nullopt;
-
142 }
-
143
-
144 if (dp.isMember(jss::authorized))
-
145 {
-
146 auto const authorized =
-
147 parseBase58<AccountID>(dp[jss::authorized].asString());
-
148 if (!authorized)
-
149 {
-
150 jvResult[jss::error] = "malformedAuthorized";
-
151 return std::nullopt;
-
152 }
-
153 return keylet::depositPreauth(*owner, *authorized).key;
-
154 }
-
155
-
156 auto const& ac(dp[jss::authorized_credentials]);
-
157 STArray const arr = parseAuthorizeCredentials(ac);
-
158
-
159 if (arr.empty() || (arr.size() > maxCredentialsArraySize))
-
160 {
-
161 jvResult[jss::error] = "malformedAuthorizedCredentials";
-
162 return std::nullopt;
-
163 }
-
164
-
165 auto const& sorted = credentials::makeSorted(arr);
-
166 if (sorted.empty())
+
110 auto const& jsBridgeAccount = params[jss::bridge_account];
+
111 if (!jsBridgeAccount.isString())
+
112 {
+
113 return std::nullopt;
+
114 }
+
115
+
116 auto const account =
+
117 parseBase58<AccountID>(jsBridgeAccount.asString());
+
118 if (!account || account->isZero())
+
119 {
+
120 return std::nullopt;
+
121 }
+
122
+
123 // This may throw and is the reason for the `try` block. The
+
124 // try block has a larger scope so the `bridge` variable
+
125 // doesn't need to be an optional.
+
126 STXChainBridge const bridge(params[jss::bridge]);
+
127 STXChainBridge::ChainType const chainType =
+
128 STXChainBridge::srcChain(account == bridge.lockingChainDoor());
+
129
+
130 if (account != bridge.door(chainType))
+
131 return std::nullopt;
+
132
+
133 return keylet::bridge(bridge, chainType);
+
134 }
+
135 catch (...)
+
136 {
+
137 return std::nullopt;
+
138 }
+
139 }();
+
140
+
141 if (maybeKeylet)
+
142 {
+
143 return maybeKeylet->key;
+
144 }
+
145
+
146 jvResult[jss::error] = "malformedRequest";
+
147 return std::nullopt;
+
148}
+
149
+
150static std::optional<uint256>
+
151parseCheck(Json::Value const& params, Json::Value& jvResult)
+
152{
+
153 uint256 uNodeIndex;
+
154 if (!uNodeIndex.parseHex(params.asString()))
+
155 {
+
156 jvResult[jss::error] = "malformedRequest";
+
157 return std::nullopt;
+
158 }
+
159
+
160 return uNodeIndex;
+
161}
+
162
+
163static std::optional<uint256>
+
164parseCredential(Json::Value const& cred, Json::Value& jvResult)
+
165{
+
166 if (cred.isString())
167 {
-
168 jvResult[jss::error] = "malformedAuthorizedCredentials";
-
169 return std::nullopt;
-
170 }
-
171
-
172 return keylet::depositPreauth(*owner, sorted).key;
-
173}
-
174
-
175static std::optional<uint256>
-
176parseDirectory(Json::Value const& params, Json::Value& jvResult)
-
177{
-
178 if (params.isNull())
-
179 {
-
180 jvResult[jss::error] = "malformedRequest";
-
181 return std::nullopt;
-
182 }
-
183
-
184 if (!params.isObject())
-
185 {
-
186 uint256 uNodeIndex;
-
187 if (!uNodeIndex.parseHex(params.asString()))
-
188 {
-
189 jvResult[jss::error] = "malformedRequest";
-
190 return std::nullopt;
-
191 }
-
192 return uNodeIndex;
-
193 }
-
194
-
195 if (params.isMember(jss::sub_index) && !params[jss::sub_index].isIntegral())
-
196 {
-
197 jvResult[jss::error] = "malformedRequest";
-
198 return std::nullopt;
-
199 }
-
200
-
201 std::uint64_t uSubIndex =
-
202 params.isMember(jss::sub_index) ? params[jss::sub_index].asUInt() : 0;
-
203
-
204 if (params.isMember(jss::dir_root))
-
205 {
-
206 uint256 uDirRoot;
-
207
-
208 if (params.isMember(jss::owner))
-
209 {
-
210 // May not specify both dir_root and owner.
-
211 jvResult[jss::error] = "malformedRequest";
-
212 return std::nullopt;
-
213 }
-
214
-
215 if (!uDirRoot.parseHex(params[jss::dir_root].asString()))
-
216 {
-
217 jvResult[jss::error] = "malformedRequest";
-
218 return std::nullopt;
-
219 }
-
220 return keylet::page(uDirRoot, uSubIndex).key;
-
221 }
-
222
-
223 if (params.isMember(jss::owner))
-
224 {
-
225 auto const ownerID =
-
226 parseBase58<AccountID>(params[jss::owner].asString());
-
227
-
228 if (!ownerID)
-
229 {
-
230 jvResult[jss::error] = "malformedAddress";
-
231 return std::nullopt;
-
232 }
-
233
-
234 return keylet::page(keylet::ownerDir(*ownerID), uSubIndex).key;
-
235 }
-
236
-
237 jvResult[jss::error] = "malformedRequest";
-
238 return std::nullopt;
-
239}
-
240
-
241static std::optional<uint256>
-
242parseEscrow(Json::Value const& params, Json::Value& jvResult)
-
243{
-
244 if (!params.isObject())
-
245 {
-
246 uint256 uNodeIndex;
-
247 if (!uNodeIndex.parseHex(params.asString()))
-
248 {
-
249 jvResult[jss::error] = "malformedRequest";
-
250 return std::nullopt;
-
251 }
-
252
-
253 return uNodeIndex;
-
254 }
-
255
-
256 if (!params.isMember(jss::owner) || !params.isMember(jss::seq) ||
-
257 !params[jss::seq].isIntegral())
-
258 {
-
259 jvResult[jss::error] = "malformedRequest";
-
260 return std::nullopt;
-
261 }
-
262
-
263 auto const id = parseBase58<AccountID>(params[jss::owner].asString());
-
264
-
265 if (!id)
-
266 {
-
267 jvResult[jss::error] = "malformedOwner";
-
268 return std::nullopt;
-
269 }
-
270
-
271 return keylet::escrow(*id, params[jss::seq].asUInt()).key;
-
272}
-
273
-
274static std::optional<uint256>
-
275parseOffer(Json::Value const& params, Json::Value& jvResult)
-
276{
-
277 if (!params.isObject())
-
278 {
-
279 uint256 uNodeIndex;
-
280 if (!uNodeIndex.parseHex(params.asString()))
-
281 {
-
282 jvResult[jss::error] = "malformedRequest";
-
283 return std::nullopt;
-
284 }
-
285 return uNodeIndex;
+
168 uint256 uNodeIndex;
+
169 if (!uNodeIndex.parseHex(cred.asString()))
+
170 {
+
171 jvResult[jss::error] = "malformedRequest";
+
172 return std::nullopt;
+
173 }
+
174 return uNodeIndex;
+
175 }
+
176
+
177 if ((!cred.isMember(jss::subject) || !cred[jss::subject].isString()) ||
+
178 (!cred.isMember(jss::issuer) || !cred[jss::issuer].isString()) ||
+
179 (!cred.isMember(jss::credential_type) ||
+
180 !cred[jss::credential_type].isString()))
+
181 {
+
182 jvResult[jss::error] = "malformedRequest";
+
183 return std::nullopt;
+
184 }
+
185
+
186 auto const subject = parseBase58<AccountID>(cred[jss::subject].asString());
+
187 auto const issuer = parseBase58<AccountID>(cred[jss::issuer].asString());
+
188 auto const credType = strUnHex(cred[jss::credential_type].asString());
+
189
+
190 if (!subject || subject->isZero() || !issuer || issuer->isZero() ||
+
191 !credType || credType->empty())
+
192 {
+
193 jvResult[jss::error] = "malformedRequest";
+
194 return std::nullopt;
+
195 }
+
196
+
197 return keylet::credential(
+
198 *subject, *issuer, Slice(credType->data(), credType->size()))
+
199 .key;
+
200}
+
201
+
202static STArray
+
203parseAuthorizeCredentials(Json::Value const& jv)
+
204{
+
205 STArray arr(sfAuthorizeCredentials, jv.size());
+
206 for (auto const& jo : jv)
+
207 {
+
208 if (!jo.isObject() || //
+
209 !jo.isMember(jss::issuer) || !jo[jss::issuer].isString() ||
+
210 !jo.isMember(jss::credential_type) ||
+
211 !jo[jss::credential_type].isString())
+
212 return {};
+
213
+
214 auto const issuer = parseBase58<AccountID>(jo[jss::issuer].asString());
+
215 if (!issuer || !*issuer)
+
216 return {};
+
217
+
218 auto const credentialType =
+
219 strUnHex(jo[jss::credential_type].asString());
+
220 if (!credentialType || credentialType->empty() ||
+
221 credentialType->size() > maxCredentialTypeLength)
+
222 return {};
+
223
+
224 auto credential = STObject::makeInnerObject(sfCredential);
+
225 credential.setAccountID(sfIssuer, *issuer);
+
226 credential.setFieldVL(sfCredentialType, *credentialType);
+
227 arr.push_back(std::move(credential));
+
228 }
+
229
+
230 return arr;
+
231}
+
232
+
233static std::optional<uint256>
+
234parseDepositPreauth(Json::Value const& dp, Json::Value& jvResult)
+
235{
+
236 if (!dp.isObject())
+
237 {
+
238 uint256 uNodeIndex;
+
239 if (!dp.isString() || !uNodeIndex.parseHex(dp.asString()))
+
240 {
+
241 jvResult[jss::error] = "malformedRequest";
+
242 return std::nullopt;
+
243 }
+
244 return uNodeIndex;
+
245 }
+
246
+
247 // clang-format off
+
248 if (
+
249 (!dp.isMember(jss::owner) || !dp[jss::owner].isString()) ||
+
250 (dp.isMember(jss::authorized) == dp.isMember(jss::authorized_credentials)) ||
+
251 (dp.isMember(jss::authorized) && !dp[jss::authorized].isString()) ||
+
252 (dp.isMember(jss::authorized_credentials) && !dp[jss::authorized_credentials].isArray())
+
253 )
+
254 // clang-format on
+
255 {
+
256 jvResult[jss::error] = "malformedRequest";
+
257 return std::nullopt;
+
258 }
+
259
+
260 auto const owner = parseBase58<AccountID>(dp[jss::owner].asString());
+
261 if (!owner)
+
262 {
+
263 jvResult[jss::error] = "malformedOwner";
+
264 return std::nullopt;
+
265 }
+
266
+
267 if (dp.isMember(jss::authorized))
+
268 {
+
269 auto const authorized =
+
270 parseBase58<AccountID>(dp[jss::authorized].asString());
+
271 if (!authorized)
+
272 {
+
273 jvResult[jss::error] = "malformedAuthorized";
+
274 return std::nullopt;
+
275 }
+
276 return keylet::depositPreauth(*owner, *authorized).key;
+
277 }
+
278
+
279 auto const& ac(dp[jss::authorized_credentials]);
+
280 STArray const arr = parseAuthorizeCredentials(ac);
+
281
+
282 if (arr.empty() || (arr.size() > maxCredentialsArraySize))
+
283 {
+
284 jvResult[jss::error] = "malformedAuthorizedCredentials";
+
285 return std::nullopt;
286 }
287
-
288 if (!params.isMember(jss::account) || !params.isMember(jss::seq) ||
-
289 !params[jss::seq].isIntegral())
+
288 auto const& sorted = credentials::makeSorted(arr);
+
289 if (sorted.empty())
290 {
-
291 jvResult[jss::error] = "malformedRequest";
+
291 jvResult[jss::error] = "malformedAuthorizedCredentials";
292 return std::nullopt;
293 }
294
-
295 auto const id = parseBase58<AccountID>(params[jss::account].asString());
-
296 if (!id)
-
297 {
-
298 jvResult[jss::error] = "malformedAddress";
-
299 return std::nullopt;
-
300 }
-
301
-
302 return keylet::offer(*id, params[jss::seq].asUInt()).key;
-
303}
-
304
-
305static std::optional<uint256>
-
306parsePaymentChannel(Json::Value const& params, Json::Value& jvResult)
-
307{
-
308 uint256 uNodeIndex;
-
309 if (!uNodeIndex.parseHex(params.asString()))
-
310 {
-
311 jvResult[jss::error] = "malformedRequest";
-
312 return std::nullopt;
-
313 }
-
314
-
315 return uNodeIndex;
-
316}
-
317
-
318static std::optional<uint256>
-
319parseRippleState(Json::Value const& jvRippleState, Json::Value& jvResult)
-
320{
-
321 Currency uCurrency;
-
322
-
323 if (!jvRippleState.isObject() || !jvRippleState.isMember(jss::currency) ||
-
324 !jvRippleState.isMember(jss::accounts) ||
-
325 !jvRippleState[jss::accounts].isArray() ||
-
326 2 != jvRippleState[jss::accounts].size() ||
-
327 !jvRippleState[jss::accounts][0u].isString() ||
-
328 !jvRippleState[jss::accounts][1u].isString() ||
-
329 (jvRippleState[jss::accounts][0u].asString() ==
-
330 jvRippleState[jss::accounts][1u].asString()))
-
331 {
-
332 jvResult[jss::error] = "malformedRequest";
-
333 return std::nullopt;
-
334 }
-
335
-
336 auto const id1 =
-
337 parseBase58<AccountID>(jvRippleState[jss::accounts][0u].asString());
-
338 auto const id2 =
-
339 parseBase58<AccountID>(jvRippleState[jss::accounts][1u].asString());
-
340 if (!id1 || !id2)
+
295 return keylet::depositPreauth(*owner, sorted).key;
+
296}
+
297
+
298static std::optional<uint256>
+
299parseDID(Json::Value const& params, Json::Value& jvResult)
+
300{
+
301 auto const account = parseBase58<AccountID>(params.asString());
+
302 if (!account || account->isZero())
+
303 {
+
304 jvResult[jss::error] = "malformedAddress";
+
305 return std::nullopt;
+
306 }
+
307
+
308 return keylet::did(*account).key;
+
309}
+
310
+
311static std::optional<uint256>
+
312parseDirectory(Json::Value const& params, Json::Value& jvResult)
+
313{
+
314 if (params.isNull())
+
315 {
+
316 jvResult[jss::error] = "malformedRequest";
+
317 return std::nullopt;
+
318 }
+
319
+
320 if (!params.isObject())
+
321 {
+
322 uint256 uNodeIndex;
+
323 if (!uNodeIndex.parseHex(params.asString()))
+
324 {
+
325 jvResult[jss::error] = "malformedRequest";
+
326 return std::nullopt;
+
327 }
+
328 return uNodeIndex;
+
329 }
+
330
+
331 if (params.isMember(jss::sub_index) && !params[jss::sub_index].isIntegral())
+
332 {
+
333 jvResult[jss::error] = "malformedRequest";
+
334 return std::nullopt;
+
335 }
+
336
+
337 std::uint64_t uSubIndex =
+
338 params.isMember(jss::sub_index) ? params[jss::sub_index].asUInt() : 0;
+
339
+
340 if (params.isMember(jss::dir_root))
341 {
-
342 jvResult[jss::error] = "malformedAddress";
-
343 return std::nullopt;
-
344 }
-
345
-
346 if (!to_currency(uCurrency, jvRippleState[jss::currency].asString()))
-
347 {
-
348 jvResult[jss::error] = "malformedCurrency";
-
349 return std::nullopt;
-
350 }
-
351
-
352 return keylet::line(*id1, *id2, uCurrency).key;
-
353}
-
354
-
355static std::optional<uint256>
-
356parseTicket(Json::Value const& params, Json::Value& jvResult)
-
357{
-
358 if (!params.isObject())
-
359 {
-
360 uint256 uNodeIndex;
-
361 if (!uNodeIndex.parseHex(params.asString()))
-
362 {
-
363 jvResult[jss::error] = "malformedRequest";
-
364 return std::nullopt;
-
365 }
-
366 return uNodeIndex;
-
367 }
-
368
-
369 if (!params.isMember(jss::account) || !params.isMember(jss::ticket_seq) ||
-
370 !params[jss::ticket_seq].isIntegral())
-
371 {
-
372 jvResult[jss::error] = "malformedRequest";
-
373 return std::nullopt;
-
374 }
-
375
-
376 auto const id = parseBase58<AccountID>(params[jss::account].asString());
-
377 if (!id)
-
378 {
-
379 jvResult[jss::error] = "malformedAddress";
-
380 return std::nullopt;
-
381 }
-
382
-
383 return getTicketIndex(*id, params[jss::ticket_seq].asUInt());
-
384}
-
385
-
386static std::optional<uint256>
-
387parseNFTokenPage(Json::Value const& params, Json::Value& jvResult)
-
388{
-
389 if (params.isString())
-
390 {
-
391 uint256 uNodeIndex;
-
392 if (!uNodeIndex.parseHex(params.asString()))
-
393 {
-
394 jvResult[jss::error] = "malformedRequest";
-
395 return std::nullopt;
-
396 }
-
397 return uNodeIndex;
-
398 }
-
399
-
400 jvResult[jss::error] = "malformedRequest";
-
401 return std::nullopt;
-
402}
-
403
-
404static std::optional<uint256>
-
405parseAMM(Json::Value const& params, Json::Value& jvResult)
-
406{
-
407 if (!params.isObject())
-
408 {
-
409 uint256 uNodeIndex;
-
410 if (!uNodeIndex.parseHex(params.asString()))
-
411 {
-
412 jvResult[jss::error] = "malformedRequest";
-
413 return std::nullopt;
-
414 }
-
415 return uNodeIndex;
-
416 }
-
417
-
418 if (!params.isMember(jss::asset) || !params.isMember(jss::asset2))
-
419 {
-
420 jvResult[jss::error] = "malformedRequest";
-
421 return std::nullopt;
+
342 uint256 uDirRoot;
+
343
+
344 if (params.isMember(jss::owner))
+
345 {
+
346 // May not specify both dir_root and owner.
+
347 jvResult[jss::error] = "malformedRequest";
+
348 return std::nullopt;
+
349 }
+
350
+
351 if (!uDirRoot.parseHex(params[jss::dir_root].asString()))
+
352 {
+
353 jvResult[jss::error] = "malformedRequest";
+
354 return std::nullopt;
+
355 }
+
356 return keylet::page(uDirRoot, uSubIndex).key;
+
357 }
+
358
+
359 if (params.isMember(jss::owner))
+
360 {
+
361 auto const ownerID =
+
362 parseBase58<AccountID>(params[jss::owner].asString());
+
363
+
364 if (!ownerID)
+
365 {
+
366 jvResult[jss::error] = "malformedAddress";
+
367 return std::nullopt;
+
368 }
+
369
+
370 return keylet::page(keylet::ownerDir(*ownerID), uSubIndex).key;
+
371 }
+
372
+
373 jvResult[jss::error] = "malformedRequest";
+
374 return std::nullopt;
+
375}
+
376
+
377static std::optional<uint256>
+
378parseEscrow(Json::Value const& params, Json::Value& jvResult)
+
379{
+
380 if (!params.isObject())
+
381 {
+
382 uint256 uNodeIndex;
+
383 if (!uNodeIndex.parseHex(params.asString()))
+
384 {
+
385 jvResult[jss::error] = "malformedRequest";
+
386 return std::nullopt;
+
387 }
+
388
+
389 return uNodeIndex;
+
390 }
+
391
+
392 if (!params.isMember(jss::owner) || !params.isMember(jss::seq) ||
+
393 !params[jss::seq].isIntegral())
+
394 {
+
395 jvResult[jss::error] = "malformedRequest";
+
396 return std::nullopt;
+
397 }
+
398
+
399 auto const id = parseBase58<AccountID>(params[jss::owner].asString());
+
400
+
401 if (!id)
+
402 {
+
403 jvResult[jss::error] = "malformedOwner";
+
404 return std::nullopt;
+
405 }
+
406
+
407 return keylet::escrow(*id, params[jss::seq].asUInt()).key;
+
408}
+
409
+
410static std::optional<uint256>
+
411parseMPToken(Json::Value const& mptJson, Json::Value& jvResult)
+
412{
+
413 if (!mptJson.isObject())
+
414 {
+
415 uint256 uNodeIndex;
+
416 if (!uNodeIndex.parseHex(mptJson.asString()))
+
417 {
+
418 jvResult[jss::error] = "malformedRequest";
+
419 return std::nullopt;
+
420 }
+
421 return uNodeIndex;
422 }
423
-
424 try
-
425 {
-
426 auto const issue = issueFromJson(params[jss::asset]);
-
427 auto const issue2 = issueFromJson(params[jss::asset2]);
-
428 return keylet::amm(issue, issue2).key;
+
424 if (!mptJson.isMember(jss::mpt_issuance_id) ||
+
425 !mptJson.isMember(jss::account))
+
426 {
+
427 jvResult[jss::error] = "malformedRequest";
+
428 return std::nullopt;
429 }
-
430 catch (std::runtime_error const&)
-
431 {
-
432 jvResult[jss::error] = "malformedRequest";
-
433 return std::nullopt;
-
434 }
-
435}
-
436
-
437static std::optional<uint256>
-
438parseBridge(Json::Value const& params, Json::Value& jvResult)
-
439{
-
440 // return the keylet for the specified bridge or nullopt if the
-
441 // request is malformed
-
442 auto const maybeKeylet = [&]() -> std::optional<Keylet> {
-
443 try
-
444 {
-
445 if (!params.isMember(jss::bridge_account))
-
446 return std::nullopt;
+
430
+
431 try
+
432 {
+
433 auto const mptIssuanceIdStr = mptJson[jss::mpt_issuance_id].asString();
+
434
+
435 uint192 mptIssuanceID;
+
436 if (!mptIssuanceID.parseHex(mptIssuanceIdStr))
+
437 Throw<std::runtime_error>("Cannot parse mpt_issuance_id");
+
438
+
439 auto const account =
+
440 parseBase58<AccountID>(mptJson[jss::account].asString());
+
441
+
442 if (!account || account->isZero())
+
443 {
+
444 jvResult[jss::error] = "malformedAddress";
+
445 return std::nullopt;
+
446 }
447
-
448 auto const& jsBridgeAccount = params[jss::bridge_account];
-
449 if (!jsBridgeAccount.isString())
-
450 {
-
451 return std::nullopt;
-
452 }
-
453
-
454 auto const account =
-
455 parseBase58<AccountID>(jsBridgeAccount.asString());
-
456 if (!account || account->isZero())
-
457 {
-
458 return std::nullopt;
-
459 }
-
460
-
461 // This may throw and is the reason for the `try` block. The
-
462 // try block has a larger scope so the `bridge` variable
-
463 // doesn't need to be an optional.
-
464 STXChainBridge const bridge(params[jss::bridge]);
-
465 STXChainBridge::ChainType const chainType =
-
466 STXChainBridge::srcChain(account == bridge.lockingChainDoor());
-
467
-
468 if (account != bridge.door(chainType))
-
469 return std::nullopt;
+
448 return keylet::mptoken(mptIssuanceID, *account).key;
+
449 }
+
450 catch (std::runtime_error const&)
+
451 {
+
452 jvResult[jss::error] = "malformedRequest";
+
453 return std::nullopt;
+
454 }
+
455}
+
456
+
457static std::optional<uint256>
+
458parseMPTokenIssuance(
+
459 Json::Value const& unparsedMPTIssuanceID,
+
460 Json::Value& jvResult)
+
461{
+
462 if (unparsedMPTIssuanceID.isString())
+
463 {
+
464 uint192 mptIssuanceID;
+
465 if (!mptIssuanceID.parseHex(unparsedMPTIssuanceID.asString()))
+
466 {
+
467 jvResult[jss::error] = "malformedRequest";
+
468 return std::nullopt;
+
469 }
470
-
471 return keylet::bridge(bridge, chainType);
-
472 }
-
473 catch (...)
-
474 {
-
475 return std::nullopt;
-
476 }
-
477 }();
-
478
-
479 if (maybeKeylet)
-
480 {
-
481 return maybeKeylet->key;
-
482 }
-
483
-
484 jvResult[jss::error] = "malformedRequest";
-
485 return std::nullopt;
-
486}
-
487
-
488static std::optional<uint256>
-
489parseXChainOwnedClaimID(Json::Value const& claim_id, Json::Value& jvResult)
-
490{
-
491 if (claim_id.isString())
-
492 {
-
493 uint256 uNodeIndex;
-
494 // we accept a node id as specifier of a xchain claim id
-
495 if (!uNodeIndex.parseHex(claim_id.asString()))
-
496 {
-
497 jvResult[jss::error] = "malformedRequest";
-
498 return std::nullopt;
-
499 }
-
500 return uNodeIndex;
-
501 }
-
502
-
503 if (!claim_id.isObject() ||
-
504 !(claim_id.isMember(sfIssuingChainDoor.getJsonName()) &&
-
505 claim_id[sfIssuingChainDoor.getJsonName()].isString()) ||
-
506 !(claim_id.isMember(sfLockingChainDoor.getJsonName()) &&
-
507 claim_id[sfLockingChainDoor.getJsonName()].isString()) ||
-
508 !claim_id.isMember(sfIssuingChainIssue.getJsonName()) ||
-
509 !claim_id.isMember(sfLockingChainIssue.getJsonName()) ||
-
510 !claim_id.isMember(jss::xchain_owned_claim_id))
-
511 {
-
512 jvResult[jss::error] = "malformedRequest";
-
513 return std::nullopt;
-
514 }
-
515
-
516 // if not specified with a node id, a claim_id is specified by
-
517 // four strings defining the bridge (locking_chain_door,
-
518 // locking_chain_issue, issuing_chain_door, issuing_chain_issue)
-
519 // and the claim id sequence number.
-
520 auto const lockingChainDoor = parseBase58<AccountID>(
-
521 claim_id[sfLockingChainDoor.getJsonName()].asString());
-
522 auto const issuingChainDoor = parseBase58<AccountID>(
-
523 claim_id[sfIssuingChainDoor.getJsonName()].asString());
-
524 Issue lockingChainIssue, issuingChainIssue;
-
525 bool valid = lockingChainDoor && issuingChainDoor;
+
471 return keylet::mptIssuance(mptIssuanceID).key;
+
472 }
+
473
+
474 jvResult[jss::error] = "malformedRequest";
+
475 return std::nullopt;
+
476}
+
477
+
478static std::optional<uint256>
+
479parseNFTokenPage(Json::Value const& params, Json::Value& jvResult)
+
480{
+
481 if (params.isString())
+
482 {
+
483 uint256 uNodeIndex;
+
484 if (!uNodeIndex.parseHex(params.asString()))
+
485 {
+
486 jvResult[jss::error] = "malformedRequest";
+
487 return std::nullopt;
+
488 }
+
489 return uNodeIndex;
+
490 }
+
491
+
492 jvResult[jss::error] = "malformedRequest";
+
493 return std::nullopt;
+
494}
+
495
+
496static std::optional<uint256>
+
497parseOffer(Json::Value const& params, Json::Value& jvResult)
+
498{
+
499 if (!params.isObject())
+
500 {
+
501 uint256 uNodeIndex;
+
502 if (!uNodeIndex.parseHex(params.asString()))
+
503 {
+
504 jvResult[jss::error] = "malformedRequest";
+
505 return std::nullopt;
+
506 }
+
507 return uNodeIndex;
+
508 }
+
509
+
510 if (!params.isMember(jss::account) || !params.isMember(jss::seq) ||
+
511 !params[jss::seq].isIntegral())
+
512 {
+
513 jvResult[jss::error] = "malformedRequest";
+
514 return std::nullopt;
+
515 }
+
516
+
517 auto const id = parseBase58<AccountID>(params[jss::account].asString());
+
518 if (!id)
+
519 {
+
520 jvResult[jss::error] = "malformedAddress";
+
521 return std::nullopt;
+
522 }
+
523
+
524 return keylet::offer(*id, params[jss::seq].asUInt()).key;
+
525}
526
-
527 if (valid)
-
528 {
-
529 try
-
530 {
-
531 lockingChainIssue =
-
532 issueFromJson(claim_id[sfLockingChainIssue.getJsonName()]);
-
533 issuingChainIssue =
-
534 issueFromJson(claim_id[sfIssuingChainIssue.getJsonName()]);
-
535 }
-
536 catch (std::runtime_error const& ex)
-
537 {
-
538 jvResult[jss::error] = "malformedRequest";
-
539 return std::nullopt;
-
540 }
-
541 }
-
542
-
543 if (valid && claim_id[jss::xchain_owned_claim_id].isIntegral())
-
544 {
-
545 auto const seq = claim_id[jss::xchain_owned_claim_id].asUInt();
-
546
-
547 STXChainBridge bridge_spec(
-
548 *lockingChainDoor,
-
549 lockingChainIssue,
-
550 *issuingChainDoor,
-
551 issuingChainIssue);
-
552 Keylet keylet = keylet::xChainClaimID(bridge_spec, seq);
-
553 return keylet.key;
-
554 }
-
555
-
556 jvResult[jss::error] = "malformedRequest";
-
557 return std::nullopt;
-
558}
-
559
-
560static std::optional<uint256>
-
561parseXChainOwnedCreateAccountClaimID(
-
562 Json::Value const& claim_id,
-
563 Json::Value& jvResult)
-
564{
-
565 if (claim_id.isString())
-
566 {
-
567 uint256 uNodeIndex;
-
568 // we accept a node id as specifier of a xchain create account
-
569 // claim_id
-
570 if (!uNodeIndex.parseHex(claim_id.asString()))
-
571 {
-
572 jvResult[jss::error] = "malformedRequest";
-
573 return std::nullopt;
-
574 }
-
575 return uNodeIndex;
+
527static std::optional<uint256>
+
528parseOracle(Json::Value const& params, Json::Value& jvResult)
+
529{
+
530 if (!params.isObject())
+
531 {
+
532 uint256 uNodeIndex;
+
533 if (!uNodeIndex.parseHex(params.asString()))
+
534 {
+
535 jvResult[jss::error] = "malformedRequest";
+
536 return std::nullopt;
+
537 }
+
538 return uNodeIndex;
+
539 }
+
540
+
541 if (!params.isMember(jss::oracle_document_id) ||
+
542 !params.isMember(jss::account))
+
543 {
+
544 jvResult[jss::error] = "malformedRequest";
+
545 return std::nullopt;
+
546 }
+
547
+
548 auto const& oracle = params;
+
549 auto const documentID = [&]() -> std::optional<std::uint32_t> {
+
550 auto const id = oracle[jss::oracle_document_id];
+
551 if (id.isUInt() || (id.isInt() && id.asInt() >= 0))
+
552 return std::make_optional(id.asUInt());
+
553
+
554 if (id.isString())
+
555 {
+
556 std::uint32_t v;
+
557 if (beast::lexicalCastChecked(v, id.asString()))
+
558 return std::make_optional(v);
+
559 }
+
560
+
561 return std::nullopt;
+
562 }();
+
563
+
564 auto const account =
+
565 parseBase58<AccountID>(oracle[jss::account].asString());
+
566 if (!account || account->isZero())
+
567 {
+
568 jvResult[jss::error] = "malformedAddress";
+
569 return std::nullopt;
+
570 }
+
571
+
572 if (!documentID)
+
573 {
+
574 jvResult[jss::error] = "malformedDocumentID";
+
575 return std::nullopt;
576 }
577
-
578 if (!claim_id.isObject() ||
-
579 !(claim_id.isMember(sfIssuingChainDoor.getJsonName()) &&
-
580 claim_id[sfIssuingChainDoor.getJsonName()].isString()) ||
-
581 !(claim_id.isMember(sfLockingChainDoor.getJsonName()) &&
-
582 claim_id[sfLockingChainDoor.getJsonName()].isString()) ||
-
583 !claim_id.isMember(sfIssuingChainIssue.getJsonName()) ||
-
584 !claim_id.isMember(sfLockingChainIssue.getJsonName()) ||
-
585 !claim_id.isMember(jss::xchain_owned_create_account_claim_id))
+
578 return keylet::oracle(*account, *documentID).key;
+
579}
+
580
+
581static std::optional<uint256>
+
582parsePaymentChannel(Json::Value const& params, Json::Value& jvResult)
+
583{
+
584 uint256 uNodeIndex;
+
585 if (!uNodeIndex.parseHex(params.asString()))
586 {
587 jvResult[jss::error] = "malformedRequest";
588 return std::nullopt;
589 }
590
-
591 // if not specified with a node id, a create account claim_id is
-
592 // specified by four strings defining the bridge
-
593 // (locking_chain_door, locking_chain_issue, issuing_chain_door,
-
594 // issuing_chain_issue) and the create account claim id sequence
-
595 // number.
-
596 auto const lockingChainDoor = parseBase58<AccountID>(
-
597 claim_id[sfLockingChainDoor.getJsonName()].asString());
-
598 auto const issuingChainDoor = parseBase58<AccountID>(
-
599 claim_id[sfIssuingChainDoor.getJsonName()].asString());
-
600 Issue lockingChainIssue, issuingChainIssue;
-
601 bool valid = lockingChainDoor && issuingChainDoor;
-
602 if (valid)
-
603 {
-
604 try
-
605 {
-
606 lockingChainIssue =
-
607 issueFromJson(claim_id[sfLockingChainIssue.getJsonName()]);
-
608 issuingChainIssue =
-
609 issueFromJson(claim_id[sfIssuingChainIssue.getJsonName()]);
-
610 }
-
611 catch (std::runtime_error const& ex)
-
612 {
-
613 valid = false;
-
614 jvResult[jss::error] = "malformedRequest";
-
615 }
-
616 }
-
617
-
618 if (valid &&
-
619 claim_id[jss::xchain_owned_create_account_claim_id].isIntegral())
-
620 {
-
621 auto const seq =
-
622 claim_id[jss::xchain_owned_create_account_claim_id].asUInt();
-
623
-
624 STXChainBridge bridge_spec(
-
625 *lockingChainDoor,
-
626 lockingChainIssue,
-
627 *issuingChainDoor,
-
628 issuingChainIssue);
-
629 Keylet keylet = keylet::xChainCreateAccountClaimID(bridge_spec, seq);
-
630 return keylet.key;
-
631 }
-
632
-
633 return std::nullopt;
-
634}
+
591 return uNodeIndex;
+
592}
+
593
+
594static std::optional<uint256>
+
595parsePermissionedDomains(Json::Value const& pd, Json::Value& jvResult)
+
596{
+
597 if (pd.isString())
+
598 {
+
599 auto const index = parseIndex(pd, jvResult);
+
600 return index;
+
601 }
+
602
+
603 if (!pd.isObject())
+
604 {
+
605 jvResult[jss::error] = "malformedRequest";
+
606 return std::nullopt;
+
607 }
+
608
+
609 if (!pd.isMember(jss::account))
+
610 {
+
611 jvResult[jss::error] = "malformedRequest";
+
612 return std::nullopt;
+
613 }
+
614
+
615 if (!pd[jss::account].isString())
+
616 {
+
617 jvResult[jss::error] = "malformedAddress";
+
618 return std::nullopt;
+
619 }
+
620
+
621 if (!pd.isMember(jss::seq) ||
+
622 (pd[jss::seq].isInt() && pd[jss::seq].asInt() < 0) ||
+
623 (!pd[jss::seq].isInt() && !pd[jss::seq].isUInt()))
+
624 {
+
625 jvResult[jss::error] = "malformedRequest";
+
626 return std::nullopt;
+
627 }
+
628
+
629 auto const account = parseBase58<AccountID>(pd[jss::account].asString());
+
630 if (!account)
+
631 {
+
632 jvResult[jss::error] = "malformedAddress";
+
633 return std::nullopt;
+
634 }
635
-
636static std::optional<uint256>
-
637parseDID(Json::Value const& params, Json::Value& jvResult)
-
638{
-
639 auto const account = parseBase58<AccountID>(params.asString());
-
640 if (!account || account->isZero())
-
641 {
-
642 jvResult[jss::error] = "malformedAddress";
-
643 return std::nullopt;
-
644 }
-
645
-
646 return keylet::did(*account).key;
-
647}
-
648
-
649static std::optional<uint256>
-
650parseOracle(Json::Value const& params, Json::Value& jvResult)
-
651{
-
652 if (!params.isObject())
-
653 {
-
654 uint256 uNodeIndex;
-
655 if (!uNodeIndex.parseHex(params.asString()))
-
656 {
-
657 jvResult[jss::error] = "malformedRequest";
-
658 return std::nullopt;
-
659 }
-
660 return uNodeIndex;
-
661 }
-
662
-
663 if (!params.isMember(jss::oracle_document_id) ||
-
664 !params.isMember(jss::account))
-
665 {
-
666 jvResult[jss::error] = "malformedRequest";
-
667 return std::nullopt;
-
668 }
-
669
-
670 auto const& oracle = params;
-
671 auto const documentID = [&]() -> std::optional<std::uint32_t> {
-
672 auto const id = oracle[jss::oracle_document_id];
-
673 if (id.isUInt() || (id.isInt() && id.asInt() >= 0))
-
674 return std::make_optional(id.asUInt());
+
636 return keylet::permissionedDomain(*account, pd[jss::seq].asUInt()).key;
+
637}
+
638
+
639static std::optional<uint256>
+
640parseRippleState(Json::Value const& jvRippleState, Json::Value& jvResult)
+
641{
+
642 Currency uCurrency;
+
643
+
644 if (!jvRippleState.isObject() || !jvRippleState.isMember(jss::currency) ||
+
645 !jvRippleState.isMember(jss::accounts) ||
+
646 !jvRippleState[jss::accounts].isArray() ||
+
647 2 != jvRippleState[jss::accounts].size() ||
+
648 !jvRippleState[jss::accounts][0u].isString() ||
+
649 !jvRippleState[jss::accounts][1u].isString() ||
+
650 (jvRippleState[jss::accounts][0u].asString() ==
+
651 jvRippleState[jss::accounts][1u].asString()))
+
652 {
+
653 jvResult[jss::error] = "malformedRequest";
+
654 return std::nullopt;
+
655 }
+
656
+
657 auto const id1 =
+
658 parseBase58<AccountID>(jvRippleState[jss::accounts][0u].asString());
+
659 auto const id2 =
+
660 parseBase58<AccountID>(jvRippleState[jss::accounts][1u].asString());
+
661 if (!id1 || !id2)
+
662 {
+
663 jvResult[jss::error] = "malformedAddress";
+
664 return std::nullopt;
+
665 }
+
666
+
667 if (!to_currency(uCurrency, jvRippleState[jss::currency].asString()))
+
668 {
+
669 jvResult[jss::error] = "malformedCurrency";
+
670 return std::nullopt;
+
671 }
+
672
+
673 return keylet::line(*id1, *id2, uCurrency).key;
+
674}
675
-
676 if (id.isString())
-
677 {
-
678 std::uint32_t v;
-
679 if (beast::lexicalCastChecked(v, id.asString()))
-
680 return std::make_optional(v);
-
681 }
-
682
-
683 return std::nullopt;
-
684 }();
-
685
-
686 auto const account =
-
687 parseBase58<AccountID>(oracle[jss::account].asString());
-
688 if (!account || account->isZero())
-
689 {
-
690 jvResult[jss::error] = "malformedAddress";
-
691 return std::nullopt;
-
692 }
-
693
-
694 if (!documentID)
-
695 {
-
696 jvResult[jss::error] = "malformedDocumentID";
-
697 return std::nullopt;
-
698 }
-
699
-
700 return keylet::oracle(*account, *documentID).key;
-
701}
-
702
-
703static std::optional<uint256>
-
704parseCredential(Json::Value const& cred, Json::Value& jvResult)
-
705{
-
706 if (cred.isString())
-
707 {
-
708 uint256 uNodeIndex;
-
709 if (!uNodeIndex.parseHex(cred.asString()))
-
710 {
-
711 jvResult[jss::error] = "malformedRequest";
-
712 return std::nullopt;
-
713 }
-
714 return uNodeIndex;
-
715 }
-
716
-
717 if ((!cred.isMember(jss::subject) || !cred[jss::subject].isString()) ||
-
718 (!cred.isMember(jss::issuer) || !cred[jss::issuer].isString()) ||
-
719 (!cred.isMember(jss::credential_type) ||
-
720 !cred[jss::credential_type].isString()))
-
721 {
-
722 jvResult[jss::error] = "malformedRequest";
-
723 return std::nullopt;
-
724 }
-
725
-
726 auto const subject = parseBase58<AccountID>(cred[jss::subject].asString());
-
727 auto const issuer = parseBase58<AccountID>(cred[jss::issuer].asString());
-
728 auto const credType = strUnHex(cred[jss::credential_type].asString());
-
729
-
730 if (!subject || subject->isZero() || !issuer || issuer->isZero() ||
-
731 !credType || credType->empty())
-
732 {
-
733 jvResult[jss::error] = "malformedRequest";
-
734 return std::nullopt;
-
735 }
-
736
-
737 return keylet::credential(
-
738 *subject, *issuer, Slice(credType->data(), credType->size()))
-
739 .key;
-
740}
-
741
-
742static std::optional<uint256>
-
743parseMPTokenIssuance(
-
744 Json::Value const& unparsedMPTIssuanceID,
-
745 Json::Value& jvResult)
-
746{
-
747 if (unparsedMPTIssuanceID.isString())
-
748 {
-
749 uint192 mptIssuanceID;
-
750 if (!mptIssuanceID.parseHex(unparsedMPTIssuanceID.asString()))
-
751 {
-
752 jvResult[jss::error] = "malformedRequest";
-
753 return std::nullopt;
+
676static std::optional<uint256>
+
677parseTicket(Json::Value const& params, Json::Value& jvResult)
+
678{
+
679 if (!params.isObject())
+
680 {
+
681 uint256 uNodeIndex;
+
682 if (!uNodeIndex.parseHex(params.asString()))
+
683 {
+
684 jvResult[jss::error] = "malformedRequest";
+
685 return std::nullopt;
+
686 }
+
687 return uNodeIndex;
+
688 }
+
689
+
690 if (!params.isMember(jss::account) || !params.isMember(jss::ticket_seq) ||
+
691 !params[jss::ticket_seq].isIntegral())
+
692 {
+
693 jvResult[jss::error] = "malformedRequest";
+
694 return std::nullopt;
+
695 }
+
696
+
697 auto const id = parseBase58<AccountID>(params[jss::account].asString());
+
698 if (!id)
+
699 {
+
700 jvResult[jss::error] = "malformedAddress";
+
701 return std::nullopt;
+
702 }
+
703
+
704 return getTicketIndex(*id, params[jss::ticket_seq].asUInt());
+
705}
+
706
+
707static std::optional<uint256>
+
708parseXChainOwnedClaimID(Json::Value const& claim_id, Json::Value& jvResult)
+
709{
+
710 if (claim_id.isString())
+
711 {
+
712 uint256 uNodeIndex;
+
713 // we accept a node id as specifier of a xchain claim id
+
714 if (!uNodeIndex.parseHex(claim_id.asString()))
+
715 {
+
716 jvResult[jss::error] = "malformedRequest";
+
717 return std::nullopt;
+
718 }
+
719 return uNodeIndex;
+
720 }
+
721
+
722 if (!claim_id.isObject() ||
+
723 !(claim_id.isMember(sfIssuingChainDoor.getJsonName()) &&
+
724 claim_id[sfIssuingChainDoor.getJsonName()].isString()) ||
+
725 !(claim_id.isMember(sfLockingChainDoor.getJsonName()) &&
+
726 claim_id[sfLockingChainDoor.getJsonName()].isString()) ||
+
727 !claim_id.isMember(sfIssuingChainIssue.getJsonName()) ||
+
728 !claim_id.isMember(sfLockingChainIssue.getJsonName()) ||
+
729 !claim_id.isMember(jss::xchain_owned_claim_id))
+
730 {
+
731 jvResult[jss::error] = "malformedRequest";
+
732 return std::nullopt;
+
733 }
+
734
+
735 // if not specified with a node id, a claim_id is specified by
+
736 // four strings defining the bridge (locking_chain_door,
+
737 // locking_chain_issue, issuing_chain_door, issuing_chain_issue)
+
738 // and the claim id sequence number.
+
739 auto const lockingChainDoor = parseBase58<AccountID>(
+
740 claim_id[sfLockingChainDoor.getJsonName()].asString());
+
741 auto const issuingChainDoor = parseBase58<AccountID>(
+
742 claim_id[sfIssuingChainDoor.getJsonName()].asString());
+
743 Issue lockingChainIssue, issuingChainIssue;
+
744 bool valid = lockingChainDoor && issuingChainDoor;
+
745
+
746 if (valid)
+
747 {
+
748 try
+
749 {
+
750 lockingChainIssue =
+
751 issueFromJson(claim_id[sfLockingChainIssue.getJsonName()]);
+
752 issuingChainIssue =
+
753 issueFromJson(claim_id[sfIssuingChainIssue.getJsonName()]);
754 }
-
755
-
756 return keylet::mptIssuance(mptIssuanceID).key;
-
757 }
-
758
-
759 jvResult[jss::error] = "malformedRequest";
-
760 return std::nullopt;
-
761}
-
762
-
763static std::optional<uint256>
-
764parseMPToken(Json::Value const& mptJson, Json::Value& jvResult)
-
765{
-
766 if (!mptJson.isObject())
-
767 {
-
768 uint256 uNodeIndex;
-
769 if (!uNodeIndex.parseHex(mptJson.asString()))
-
770 {
-
771 jvResult[jss::error] = "malformedRequest";
-
772 return std::nullopt;
-
773 }
-
774 return uNodeIndex;
-
775 }
-
776
-
777 if (!mptJson.isMember(jss::mpt_issuance_id) ||
-
778 !mptJson.isMember(jss::account))
-
779 {
-
780 jvResult[jss::error] = "malformedRequest";
-
781 return std::nullopt;
-
782 }
-
783
-
784 try
+
755 catch (std::runtime_error const& ex)
+
756 {
+
757 jvResult[jss::error] = "malformedRequest";
+
758 return std::nullopt;
+
759 }
+
760 }
+
761
+
762 if (valid && claim_id[jss::xchain_owned_claim_id].isIntegral())
+
763 {
+
764 auto const seq = claim_id[jss::xchain_owned_claim_id].asUInt();
+
765
+
766 STXChainBridge bridge_spec(
+
767 *lockingChainDoor,
+
768 lockingChainIssue,
+
769 *issuingChainDoor,
+
770 issuingChainIssue);
+
771 Keylet keylet = keylet::xChainClaimID(bridge_spec, seq);
+
772 return keylet.key;
+
773 }
+
774
+
775 jvResult[jss::error] = "malformedRequest";
+
776 return std::nullopt;
+
777}
+
778
+
779static std::optional<uint256>
+
780parseXChainOwnedCreateAccountClaimID(
+
781 Json::Value const& claim_id,
+
782 Json::Value& jvResult)
+
783{
+
784 if (claim_id.isString())
785 {
-
786 auto const mptIssuanceIdStr = mptJson[jss::mpt_issuance_id].asString();
-
787
-
788 uint192 mptIssuanceID;
-
789 if (!mptIssuanceID.parseHex(mptIssuanceIdStr))
-
790 Throw<std::runtime_error>("Cannot parse mpt_issuance_id");
-
791
-
792 auto const account =
-
793 parseBase58<AccountID>(mptJson[jss::account].asString());
-
794
-
795 if (!account || account->isZero())
-
796 {
-
797 jvResult[jss::error] = "malformedAddress";
-
798 return std::nullopt;
-
799 }
-
800
-
801 return keylet::mptoken(mptIssuanceID, *account).key;
-
802 }
-
803 catch (std::runtime_error const&)
-
804 {
-
805 jvResult[jss::error] = "malformedRequest";
-
806 return std::nullopt;
-
807 }
-
808}
+
786 uint256 uNodeIndex;
+
787 // we accept a node id as specifier of a xchain create account
+
788 // claim_id
+
789 if (!uNodeIndex.parseHex(claim_id.asString()))
+
790 {
+
791 jvResult[jss::error] = "malformedRequest";
+
792 return std::nullopt;
+
793 }
+
794 return uNodeIndex;
+
795 }
+
796
+
797 if (!claim_id.isObject() ||
+
798 !(claim_id.isMember(sfIssuingChainDoor.getJsonName()) &&
+
799 claim_id[sfIssuingChainDoor.getJsonName()].isString()) ||
+
800 !(claim_id.isMember(sfLockingChainDoor.getJsonName()) &&
+
801 claim_id[sfLockingChainDoor.getJsonName()].isString()) ||
+
802 !claim_id.isMember(sfIssuingChainIssue.getJsonName()) ||
+
803 !claim_id.isMember(sfLockingChainIssue.getJsonName()) ||
+
804 !claim_id.isMember(jss::xchain_owned_create_account_claim_id))
+
805 {
+
806 jvResult[jss::error] = "malformedRequest";
+
807 return std::nullopt;
+
808 }
809
-
810static std::optional<uint256>
-
811parsePermissionedDomains(Json::Value const& pd, Json::Value& jvResult)
-
812{
-
813 if (pd.isString())
-
814 {
-
815 auto const index = parseIndex(pd, jvResult);
-
816 return index;
-
817 }
-
818
-
819 if (!pd.isObject())
-
820 {
-
821 jvResult[jss::error] = "malformedRequest";
-
822 return std::nullopt;
-
823 }
-
824
-
825 if (!pd.isMember(jss::account))
-
826 {
-
827 jvResult[jss::error] = "malformedRequest";
-
828 return std::nullopt;
-
829 }
-
830
-
831 if (!pd[jss::account].isString())
-
832 {
-
833 jvResult[jss::error] = "malformedAddress";
-
834 return std::nullopt;
+
810 // if not specified with a node id, a create account claim_id is
+
811 // specified by four strings defining the bridge
+
812 // (locking_chain_door, locking_chain_issue, issuing_chain_door,
+
813 // issuing_chain_issue) and the create account claim id sequence
+
814 // number.
+
815 auto const lockingChainDoor = parseBase58<AccountID>(
+
816 claim_id[sfLockingChainDoor.getJsonName()].asString());
+
817 auto const issuingChainDoor = parseBase58<AccountID>(
+
818 claim_id[sfIssuingChainDoor.getJsonName()].asString());
+
819 Issue lockingChainIssue, issuingChainIssue;
+
820 bool valid = lockingChainDoor && issuingChainDoor;
+
821 if (valid)
+
822 {
+
823 try
+
824 {
+
825 lockingChainIssue =
+
826 issueFromJson(claim_id[sfLockingChainIssue.getJsonName()]);
+
827 issuingChainIssue =
+
828 issueFromJson(claim_id[sfIssuingChainIssue.getJsonName()]);
+
829 }
+
830 catch (std::runtime_error const& ex)
+
831 {
+
832 valid = false;
+
833 jvResult[jss::error] = "malformedRequest";
+
834 }
835 }
836
-
837 if (!pd.isMember(jss::seq) ||
-
838 (pd[jss::seq].isInt() && pd[jss::seq].asInt() < 0) ||
-
839 (!pd[jss::seq].isInt() && !pd[jss::seq].isUInt()))
-
840 {
-
841 jvResult[jss::error] = "malformedRequest";
-
842 return std::nullopt;
-
843 }
-
844
-
845 auto const account = parseBase58<AccountID>(pd[jss::account].asString());
-
846 if (!account)
-
847 {
-
848 jvResult[jss::error] = "malformedAddress";
-
849 return std::nullopt;
+
837 if (valid &&
+
838 claim_id[jss::xchain_owned_create_account_claim_id].isIntegral())
+
839 {
+
840 auto const seq =
+
841 claim_id[jss::xchain_owned_create_account_claim_id].asUInt();
+
842
+
843 STXChainBridge bridge_spec(
+
844 *lockingChainDoor,
+
845 lockingChainIssue,
+
846 *issuingChainDoor,
+
847 issuingChainIssue);
+
848 Keylet keylet = keylet::xChainCreateAccountClaimID(bridge_spec, seq);
+
849 return keylet.key;
850 }
851
-
852 return keylet::permissionedDomain(*account, pd[jss::seq].asUInt()).key;
+
852 return std::nullopt;
853}
854
855using FunctionType =
@@ -1197,41 +1197,41 @@ $(function() {
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:265
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition: Indexes.cpp:333
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
-
static std::optional< uint256 > parseMPToken(Json::Value const &mptJson, Json::Value &jvResult)
-
static std::optional< uint256 > parseIndex(Json::Value const &params, Json::Value &jvResult)
Definition: LedgerEntry.cpp:72
-
static STArray parseAuthorizeCredentials(Json::Value const &jv)
Definition: LedgerEntry.cpp:41
+
static std::optional< uint256 > parseMPToken(Json::Value const &mptJson, Json::Value &jvResult)
+
static std::optional< uint256 > parseIndex(Json::Value const &params, Json::Value &jvResult)
Definition: LedgerEntry.cpp:41
+
static STArray parseAuthorizeCredentials(Json::Value const &jv)
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
Json::Value doLedgerEntry(RPC::JsonContext &)
-
static std::optional< uint256 > parsePaymentChannel(Json::Value const &params, Json::Value &jvResult)
+
static std::optional< uint256 > parsePaymentChannel(Json::Value const &params, Json::Value &jvResult)
@ rpcINVALID_PARAMS
Definition: ErrorCodes.h:84
-
static std::optional< uint256 > parseAMM(Json::Value const &params, Json::Value &jvResult)
-
static std::optional< uint256 > parseMPTokenIssuance(Json::Value const &unparsedMPTIssuanceID, Json::Value &jvResult)
-
static std::optional< uint256 > parseBridge(Json::Value const &params, Json::Value &jvResult)
-
static std::optional< uint256 > parseDepositPreauth(Json::Value const &dp, Json::Value &jvResult)
-
static std::optional< uint256 > parseNFTokenPage(Json::Value const &params, Json::Value &jvResult)
-
static std::optional< uint256 > parseCheck(Json::Value const &params, Json::Value &jvResult)
Definition: LedgerEntry.cpp:98
-
static std::optional< uint256 > parseTicket(Json::Value const &params, Json::Value &jvResult)
-
static std::optional< uint256 > parseCredential(Json::Value const &cred, Json::Value &jvResult)
-
static std::optional< uint256 > parseOracle(Json::Value const &params, Json::Value &jvResult)
+
static std::optional< uint256 > parseAMM(Json::Value const &params, Json::Value &jvResult)
Definition: LedgerEntry.cpp:67
+
static std::optional< uint256 > parseMPTokenIssuance(Json::Value const &unparsedMPTIssuanceID, Json::Value &jvResult)
+
static std::optional< uint256 > parseBridge(Json::Value const &params, Json::Value &jvResult)
+
static std::optional< uint256 > parseDepositPreauth(Json::Value const &dp, Json::Value &jvResult)
+
static std::optional< uint256 > parseNFTokenPage(Json::Value const &params, Json::Value &jvResult)
+
static std::optional< uint256 > parseCheck(Json::Value const &params, Json::Value &jvResult)
+
static std::optional< uint256 > parseTicket(Json::Value const &params, Json::Value &jvResult)
+
static std::optional< uint256 > parseCredential(Json::Value const &cred, Json::Value &jvResult)
+
static std::optional< uint256 > parseOracle(Json::Value const &params, Json::Value &jvResult)
Issue issueFromJson(Json::Value const &v)
Definition: Issue.cpp:95
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
-
static std::optional< uint256 > parseEscrow(Json::Value const &params, Json::Value &jvResult)
+
static std::optional< uint256 > parseEscrow(Json::Value const &params, Json::Value &jvResult)
std::size_t constexpr maxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition: Protocol.h:107
std::pair< org::xrpl::rpc::v1::GetLedgerEntryResponse, grpc::Status > doLedgerEntryGrpc(RPC::GRPCContext< org::xrpl::rpc::v1::GetLedgerEntryRequest > &context)
-
static std::optional< uint256 > parseXChainOwnedCreateAccountClaimID(Json::Value const &claim_id, Json::Value &jvResult)
+
static std::optional< uint256 > parseXChainOwnedCreateAccountClaimID(Json::Value const &claim_id, Json::Value &jvResult)
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition: Protocol.h:104
uint256 getTicketIndex(AccountID const &account, std::uint32_t uSequence)
Definition: Indexes.cpp:147
-
static std::optional< uint256 > parseOffer(Json::Value const &params, Json::Value &jvResult)
+
static std::optional< uint256 > parseOffer(Json::Value const &params, Json::Value &jvResult)
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
LedgerEntryType
Identifiers for on-ledger objects.
Definition: LedgerFormats.h:54
@ ltANY
A special type, matching any ledger entry type.
Definition: LedgerFormats.h:78
-
static std::optional< uint256 > parseAccountRoot(Json::Value const &params, Json::Value &jvResult)
Definition: LedgerEntry.cpp:85
-
static std::optional< uint256 > parseRippleState(Json::Value const &jvRippleState, Json::Value &jvResult)
+
static std::optional< uint256 > parseAccountRoot(Json::Value const &params, Json::Value &jvResult)
Definition: LedgerEntry.cpp:54
+
static std::optional< uint256 > parseRippleState(Json::Value const &jvRippleState, Json::Value &jvResult)
@ credential
Credentials signature.
-
static std::optional< uint256 > parseXChainOwnedClaimID(Json::Value const &claim_id, Json::Value &jvResult)
-
static std::optional< uint256 > parseDirectory(Json::Value const &params, Json::Value &jvResult)
-
static std::optional< uint256 > parseDID(Json::Value const &params, Json::Value &jvResult)
-
static std::optional< uint256 > parsePermissionedDomains(Json::Value const &pd, Json::Value &jvResult)
+
static std::optional< uint256 > parseXChainOwnedClaimID(Json::Value const &claim_id, Json::Value &jvResult)
+
static std::optional< uint256 > parseDirectory(Json::Value const &params, Json::Value &jvResult)
+
static std::optional< uint256 > parseDID(Json::Value const &params, Json::Value &jvResult)
+
static std::optional< uint256 > parsePermissionedDomains(Json::Value const &pd, Json::Value &jvResult)
bool to_currency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition: UintTypes.cpp:84
diff --git a/LedgerEntry__test_8cpp_source.html b/LedgerEntry__test_8cpp_source.html new file mode 100644 index 0000000000..08bec9d85a --- /dev/null +++ b/LedgerEntry__test_8cpp_source.html @@ -0,0 +1,2828 @@ + + + + + + + +rippled: LedgerEntry_test.cpp Source File + + + + + + + + + +
+
+ + + + + + +
+
rippled +
+
+
+ + + + + + + + +
+
+ + +
+
+
+
+
+
Loading...
+
Searching...
+
No Matches
+
+
+
+
+ + +
+
+
LedgerEntry_test.cpp
+
+
+
1//------------------------------------------------------------------------------
+
2/*
+
3 This file is part of rippled: https://github.com/ripple/rippled
+
4 Copyright (c) 2012-2025 Ripple Labs Inc.
+
5
+
6 Permission to use, copy, modify, and/or distribute this software for any
+
7 purpose with or without fee is hereby granted, provided that the above
+
8 copyright notice and this permission notice appear in all copies.
+
9
+
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
17*/
+
18//==============================================================================
+
19
+
20#include <test/jtx.h>
+
21#include <test/jtx/Oracle.h>
+
22#include <test/jtx/attester.h>
+
23#include <test/jtx/multisign.h>
+
24#include <test/jtx/xchain_bridge.h>
+
25
+
26#include <xrpl/beast/unit_test.h>
+
27#include <xrpl/json/json_value.h>
+
28#include <xrpl/protocol/AccountID.h>
+
29#include <xrpl/protocol/ErrorCodes.h>
+
30#include <xrpl/protocol/STXChainBridge.h>
+
31#include <xrpl/protocol/jss.h>
+
32
+
33namespace ripple {
+
34
+
35namespace test {
+
36
+ +
38{
+
39 void
+ +
41 Json::Value const& jv,
+
42 std::string const& err,
+
43 std::string const& msg)
+
44 {
+
45 if (BEAST_EXPECT(jv.isMember(jss::status)))
+
46 BEAST_EXPECT(jv[jss::status] == "error");
+
47 if (BEAST_EXPECT(jv.isMember(jss::error)))
+
48 BEAST_EXPECT(jv[jss::error] == err);
+
49 if (msg.empty())
+
50 {
+
51 BEAST_EXPECT(
+
52 jv[jss::error_message] == Json::nullValue ||
+
53 jv[jss::error_message] == "");
+
54 }
+
55 else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
+
56 BEAST_EXPECT(jv[jss::error_message] == msg);
+
57 }
+
58
+
59 // Corrupt a valid address by replacing the 10th character with '!'.
+
60 // '!' is not part of the ripple alphabet.
+ + +
63 {
+
64 std::string ret = std::move(good);
+
65 ret.replace(10, 1, 1, '!');
+
66 return ret;
+
67 }
+
68
+
69 void
+ +
71 {
+
72 testcase("Invalid requests");
+
73 using namespace test::jtx;
+
74 Env env{*this};
+
75 Account const alice{"alice"};
+
76 env.fund(XRP(10000), alice);
+
77 env.close();
+
78
+
79 {
+
80 // Missing ledger_entry ledger_hash
+
81 Json::Value jvParams;
+
82 jvParams[jss::account_root] = alice.human();
+
83 jvParams[jss::ledger_hash] =
+
84 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+
85 "AA";
+
86 auto const jrr = env.rpc(
+
87 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
88 checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
+
89 }
+
90
+
91 {
+
92 // ask for an zero index
+
93 Json::Value jvParams;
+
94 jvParams[jss::ledger_index] = "validated";
+
95 jvParams[jss::index] =
+
96 "00000000000000000000000000000000000000000000000000000000000000"
+
97 "0000";
+
98 auto const jrr = env.rpc(
+
99 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
100 checkErrorValue(jrr, "malformedRequest", "");
+
101 }
+
102 }
+
103
+
104 void
+ +
106 {
+
107 testcase("ledger_entry Request AccountRoot");
+
108 using namespace test::jtx;
+
109
+
110 auto cfg = envconfig();
+
111 cfg->FEES.reference_fee = 10;
+
112 Env env{*this, std::move(cfg)};
+
113
+
114 Account const alice{"alice"};
+
115 env.fund(XRP(10000), alice);
+
116 env.close();
+
117
+
118 std::string const ledgerHash{to_string(env.closed()->info().hash)};
+
119 {
+
120 // Exercise ledger_closed along the way.
+
121 Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
+
122 BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
+
123 BEAST_EXPECT(jrr[jss::ledger_index] == 3);
+
124 }
+
125
+
126 std::string accountRootIndex;
+
127 {
+
128 // Request alice's account root.
+
129 Json::Value jvParams;
+
130 jvParams[jss::account_root] = alice.human();
+
131 jvParams[jss::ledger_hash] = ledgerHash;
+
132 Json::Value const jrr = env.rpc(
+
133 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
134 BEAST_EXPECT(jrr.isMember(jss::node));
+
135 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
+
136 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
+
137 accountRootIndex = jrr[jss::index].asString();
+
138 }
+
139 {
+
140 constexpr char alicesAcctRootBinary[]{
+
141 "1100612200800000240000000425000000032D00000000559CE54C3B934E4"
+
142 "73A995B477E92EC229F99CED5B62BF4D2ACE4DC42719103AE2F6240000002"
+
143 "540BE4008114AE123A8556F3CF91154711376AFB0F894F832B3D"};
+
144
+
145 // Request alice's account root, but with binary == true;
+
146 Json::Value jvParams;
+
147 jvParams[jss::account_root] = alice.human();
+
148 jvParams[jss::binary] = 1;
+
149 jvParams[jss::ledger_hash] = ledgerHash;
+
150 Json::Value const jrr = env.rpc(
+
151 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
152 BEAST_EXPECT(jrr.isMember(jss::node_binary));
+
153 BEAST_EXPECT(jrr[jss::node_binary] == alicesAcctRootBinary);
+
154 }
+
155 {
+
156 // Request alice's account root using the index.
+
157 Json::Value jvParams;
+
158 jvParams[jss::index] = accountRootIndex;
+
159 Json::Value const jrr = env.rpc(
+
160 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
161 BEAST_EXPECT(!jrr.isMember(jss::node_binary));
+
162 BEAST_EXPECT(jrr.isMember(jss::node));
+
163 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
+
164 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
+
165 }
+
166 {
+
167 // Request alice's account root by index, but with binary == false.
+
168 Json::Value jvParams;
+
169 jvParams[jss::index] = accountRootIndex;
+
170 jvParams[jss::binary] = 0;
+
171 Json::Value const jrr = env.rpc(
+
172 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
173 BEAST_EXPECT(jrr.isMember(jss::node));
+
174 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
+
175 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
+
176 }
+
177 {
+
178 // Request using a corrupted AccountID.
+
179 Json::Value jvParams;
+
180 jvParams[jss::account_root] = makeBadAddress(alice.human());
+
181 jvParams[jss::ledger_hash] = ledgerHash;
+
182 Json::Value const jrr = env.rpc(
+
183 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
184 checkErrorValue(jrr, "malformedAddress", "");
+
185 }
+
186 {
+
187 // Request an account that is not in the ledger.
+
188 Json::Value jvParams;
+
189 jvParams[jss::account_root] = Account("bob").human();
+
190 jvParams[jss::ledger_hash] = ledgerHash;
+
191 Json::Value const jrr = env.rpc(
+
192 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
193 checkErrorValue(jrr, "entryNotFound", "");
+
194 }
+
195 }
+
196
+
197 void
+ +
199 {
+
200 testcase("ledger_entry Request Check");
+
201 using namespace test::jtx;
+
202 Env env{*this};
+
203 Account const alice{"alice"};
+
204 env.fund(XRP(10000), alice);
+
205 env.close();
+
206
+
207 auto const checkId = keylet::check(env.master, env.seq(env.master));
+
208
+
209 env(check::create(env.master, alice, XRP(100)));
+
210 env.close();
+
211
+
212 std::string const ledgerHash{to_string(env.closed()->info().hash)};
+
213 {
+
214 // Request a check.
+
215 Json::Value jvParams;
+
216 jvParams[jss::check] = to_string(checkId.key);
+
217 jvParams[jss::ledger_hash] = ledgerHash;
+
218 Json::Value const jrr = env.rpc(
+
219 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
220 BEAST_EXPECT(
+
221 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
+
222 BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
+
223 }
+
224 {
+
225 // Request an index that is not a check. We'll use alice's
+
226 // account root index.
+
227 std::string accountRootIndex;
+
228 {
+
229 Json::Value jvParams;
+
230 jvParams[jss::account_root] = alice.human();
+
231 Json::Value const jrr = env.rpc(
+
232 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
233 accountRootIndex = jrr[jss::index].asString();
+
234 }
+
235 Json::Value jvParams;
+
236 jvParams[jss::check] = accountRootIndex;
+
237 jvParams[jss::ledger_hash] = ledgerHash;
+
238 Json::Value const jrr = env.rpc(
+
239 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
240 checkErrorValue(jrr, "unexpectedLedgerType", "");
+
241 }
+
242 }
+
243
+
244 void
+ +
246 {
+
247 testcase("ledger_entry credentials");
+
248
+
249 using namespace test::jtx;
+
250
+
251 Env env(*this);
+
252 Account const issuer{"issuer"};
+
253 Account const alice{"alice"};
+
254 Account const bob{"bob"};
+
255 const char credType[] = "abcde";
+
256
+
257 env.fund(XRP(5000), issuer, alice, bob);
+
258 env.close();
+
259
+
260 // Setup credentials with DepositAuth object for Alice and Bob
+
261 env(credentials::create(alice, issuer, credType));
+
262 env.close();
+
263
+
264 {
+
265 // Succeed
+
266 auto jv = credentials::ledgerEntry(env, alice, issuer, credType);
+
267 BEAST_EXPECT(
+
268 jv.isObject() && jv.isMember(jss::result) &&
+
269 !jv[jss::result].isMember(jss::error) &&
+
270 jv[jss::result].isMember(jss::node) &&
+
271 jv[jss::result][jss::node].isMember(
+
272 sfLedgerEntryType.jsonName) &&
+
273 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
+
274 jss::Credential);
+
275
+
276 std::string const credIdx = jv[jss::result][jss::index].asString();
+
277
+
278 jv = credentials::ledgerEntry(env, credIdx);
+
279 BEAST_EXPECT(
+
280 jv.isObject() && jv.isMember(jss::result) &&
+
281 !jv[jss::result].isMember(jss::error) &&
+
282 jv[jss::result].isMember(jss::node) &&
+
283 jv[jss::result][jss::node].isMember(
+
284 sfLedgerEntryType.jsonName) &&
+
285 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
+
286 jss::Credential);
+
287 }
+
288
+
289 {
+
290 // Fail, index not a hash
+
291 auto const jv = credentials::ledgerEntry(env, "");
+
292 checkErrorValue(jv[jss::result], "malformedRequest", "");
+
293 }
+
294
+
295 {
+
296 // Fail, credential doesn't exist
+
297 auto const jv = credentials::ledgerEntry(
+
298 env,
+
299 "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
+
300 "E4");
+
301 checkErrorValue(jv[jss::result], "entryNotFound", "");
+
302 }
+
303
+
304 {
+
305 // Fail, invalid subject
+
306 Json::Value jv;
+
307 jv[jss::ledger_index] = jss::validated;
+
308 jv[jss::credential][jss::subject] = 42;
+
309 jv[jss::credential][jss::issuer] = issuer.human();
+
310 jv[jss::credential][jss::credential_type] =
+
311 strHex(std::string_view(credType));
+
312 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
+
313 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
314 }
+
315
+
316 {
+
317 // Fail, invalid issuer
+
318 Json::Value jv;
+
319 jv[jss::ledger_index] = jss::validated;
+
320 jv[jss::credential][jss::subject] = alice.human();
+
321 jv[jss::credential][jss::issuer] = 42;
+
322 jv[jss::credential][jss::credential_type] =
+
323 strHex(std::string_view(credType));
+
324 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
+
325 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
326 }
+
327
+
328 {
+
329 // Fail, invalid credentials type
+
330 Json::Value jv;
+
331 jv[jss::ledger_index] = jss::validated;
+
332 jv[jss::credential][jss::subject] = alice.human();
+
333 jv[jss::credential][jss::issuer] = issuer.human();
+
334 jv[jss::credential][jss::credential_type] = 42;
+
335 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
+
336 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
337 }
+
338
+
339 {
+
340 // Fail, empty subject
+
341 Json::Value jv;
+
342 jv[jss::ledger_index] = jss::validated;
+
343 jv[jss::credential][jss::subject] = "";
+
344 jv[jss::credential][jss::issuer] = issuer.human();
+
345 jv[jss::credential][jss::credential_type] =
+
346 strHex(std::string_view(credType));
+
347 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
+
348 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
349 }
+
350
+
351 {
+
352 // Fail, empty issuer
+
353 Json::Value jv;
+
354 jv[jss::ledger_index] = jss::validated;
+
355 jv[jss::credential][jss::subject] = alice.human();
+
356 jv[jss::credential][jss::issuer] = "";
+
357 jv[jss::credential][jss::credential_type] =
+
358 strHex(std::string_view(credType));
+
359 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
+
360 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
361 }
+
362
+
363 {
+
364 // Fail, empty credentials type
+
365 Json::Value jv;
+
366 jv[jss::ledger_index] = jss::validated;
+
367 jv[jss::credential][jss::subject] = alice.human();
+
368 jv[jss::credential][jss::issuer] = issuer.human();
+
369 jv[jss::credential][jss::credential_type] = "";
+
370 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
+
371 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
372 }
+
373
+
374 {
+
375 // Fail, no subject
+
376 Json::Value jv;
+
377 jv[jss::ledger_index] = jss::validated;
+
378 jv[jss::credential][jss::issuer] = issuer.human();
+
379 jv[jss::credential][jss::credential_type] =
+
380 strHex(std::string_view(credType));
+
381 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
+
382 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
383 }
+
384
+
385 {
+
386 // Fail, no issuer
+
387 Json::Value jv;
+
388 jv[jss::ledger_index] = jss::validated;
+
389 jv[jss::credential][jss::subject] = alice.human();
+
390 jv[jss::credential][jss::credential_type] =
+
391 strHex(std::string_view(credType));
+
392 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
+
393 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
394 }
+
395
+
396 {
+
397 // Fail, no credentials type
+
398 Json::Value jv;
+
399 jv[jss::ledger_index] = jss::validated;
+
400 jv[jss::credential][jss::subject] = alice.human();
+
401 jv[jss::credential][jss::issuer] = issuer.human();
+
402 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
+
403 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
404 }
+
405
+
406 {
+
407 // Fail, not AccountID subject
+
408 Json::Value jv;
+
409 jv[jss::ledger_index] = jss::validated;
+
410 jv[jss::credential][jss::subject] = "wehsdbvasbdfvj";
+
411 jv[jss::credential][jss::issuer] = issuer.human();
+
412 jv[jss::credential][jss::credential_type] =
+
413 strHex(std::string_view(credType));
+
414 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
+
415 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
416 }
+
417
+
418 {
+
419 // Fail, not AccountID issuer
+
420 Json::Value jv;
+
421 jv[jss::ledger_index] = jss::validated;
+
422 jv[jss::credential][jss::subject] = alice.human();
+
423 jv[jss::credential][jss::issuer] = "c4p93ugndfbsiu";
+
424 jv[jss::credential][jss::credential_type] =
+
425 strHex(std::string_view(credType));
+
426 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
+
427 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
428 }
+
429
+
430 {
+
431 // Fail, credentials type isn't hex encoded
+
432 Json::Value jv;
+
433 jv[jss::ledger_index] = jss::validated;
+
434 jv[jss::credential][jss::subject] = alice.human();
+
435 jv[jss::credential][jss::issuer] = issuer.human();
+
436 jv[jss::credential][jss::credential_type] = "12KK";
+
437 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
+
438 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
439 }
+
440 }
+
441
+
442 void
+ +
444 {
+
445 testcase("ledger_entry Deposit Preauth");
+
446
+
447 using namespace test::jtx;
+
448
+
449 Env env{*this};
+
450 Account const alice{"alice"};
+
451 Account const becky{"becky"};
+
452
+
453 env.fund(XRP(10000), alice, becky);
+
454 env.close();
+
455
+
456 env(deposit::auth(alice, becky));
+
457 env.close();
+
458
+
459 std::string const ledgerHash{to_string(env.closed()->info().hash)};
+
460 std::string depositPreauthIndex;
+
461 {
+
462 // Request a depositPreauth by owner and authorized.
+
463 Json::Value jvParams;
+
464 jvParams[jss::deposit_preauth][jss::owner] = alice.human();
+
465 jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
+
466 jvParams[jss::ledger_hash] = ledgerHash;
+
467 Json::Value const jrr = env.rpc(
+
468 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
469
+
470 BEAST_EXPECT(
+
471 jrr[jss::node][sfLedgerEntryType.jsonName] ==
+
472 jss::DepositPreauth);
+
473 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
+
474 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
+
475 depositPreauthIndex = jrr[jss::node][jss::index].asString();
+
476 }
+
477 {
+
478 // Request a depositPreauth by index.
+
479 Json::Value jvParams;
+
480 jvParams[jss::deposit_preauth] = depositPreauthIndex;
+
481 jvParams[jss::ledger_hash] = ledgerHash;
+
482 Json::Value const jrr = env.rpc(
+
483 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
484
+
485 BEAST_EXPECT(
+
486 jrr[jss::node][sfLedgerEntryType.jsonName] ==
+
487 jss::DepositPreauth);
+
488 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
+
489 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
+
490 }
+
491 {
+
492 // Malformed request: deposit_preauth neither object nor string.
+
493 Json::Value jvParams;
+
494 jvParams[jss::deposit_preauth] = -5;
+
495 jvParams[jss::ledger_hash] = ledgerHash;
+
496 Json::Value const jrr = env.rpc(
+
497 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
498 checkErrorValue(jrr, "malformedRequest", "");
+
499 }
+
500 {
+
501 // Malformed request: deposit_preauth not hex string.
+
502 Json::Value jvParams;
+
503 jvParams[jss::deposit_preauth] = "0123456789ABCDEFG";
+
504 jvParams[jss::ledger_hash] = ledgerHash;
+
505 Json::Value const jrr = env.rpc(
+
506 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
507 checkErrorValue(jrr, "malformedRequest", "");
+
508 }
+
509 {
+
510 // Malformed request: missing [jss::deposit_preauth][jss::owner]
+
511 Json::Value jvParams;
+
512 jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
+
513 jvParams[jss::ledger_hash] = ledgerHash;
+
514 Json::Value const jrr = env.rpc(
+
515 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
516 checkErrorValue(jrr, "malformedRequest", "");
+
517 }
+
518 {
+
519 // Malformed request: [jss::deposit_preauth][jss::owner] not string.
+
520 Json::Value jvParams;
+
521 jvParams[jss::deposit_preauth][jss::owner] = 7;
+
522 jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
+
523 jvParams[jss::ledger_hash] = ledgerHash;
+
524 Json::Value const jrr = env.rpc(
+
525 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
526 checkErrorValue(jrr, "malformedRequest", "");
+
527 }
+
528 {
+
529 // Malformed: missing [jss::deposit_preauth][jss::authorized]
+
530 Json::Value jvParams;
+
531 jvParams[jss::deposit_preauth][jss::owner] = alice.human();
+
532 jvParams[jss::ledger_hash] = ledgerHash;
+
533 Json::Value const jrr = env.rpc(
+
534 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
535 checkErrorValue(jrr, "malformedRequest", "");
+
536 }
+
537 {
+
538 // Malformed: [jss::deposit_preauth][jss::authorized] not string.
+
539 Json::Value jvParams;
+
540 jvParams[jss::deposit_preauth][jss::owner] = alice.human();
+
541 jvParams[jss::deposit_preauth][jss::authorized] = 47;
+
542 jvParams[jss::ledger_hash] = ledgerHash;
+
543 Json::Value const jrr = env.rpc(
+
544 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
545 checkErrorValue(jrr, "malformedRequest", "");
+
546 }
+
547 {
+
548 // Malformed: [jss::deposit_preauth][jss::owner] is malformed.
+
549 Json::Value jvParams;
+
550 jvParams[jss::deposit_preauth][jss::owner] =
+
551 "rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas";
+
552
+
553 jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
+
554 jvParams[jss::ledger_hash] = ledgerHash;
+
555 Json::Value const jrr = env.rpc(
+
556 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
557 checkErrorValue(jrr, "malformedOwner", "");
+
558 }
+
559 {
+
560 // Malformed: [jss::deposit_preauth][jss::authorized] is malformed.
+
561 Json::Value jvParams;
+
562 jvParams[jss::deposit_preauth][jss::owner] = alice.human();
+
563 jvParams[jss::deposit_preauth][jss::authorized] =
+
564 "rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas";
+
565
+
566 jvParams[jss::ledger_hash] = ledgerHash;
+
567 Json::Value const jrr = env.rpc(
+
568 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
569 checkErrorValue(jrr, "malformedAuthorized", "");
+
570 }
+
571 }
+
572
+
573 void
+ +
575 {
+
576 testcase("ledger_entry Deposit Preauth with credentials");
+
577
+
578 using namespace test::jtx;
+
579
+
580 Env env(*this);
+
581 Account const issuer{"issuer"};
+
582 Account const alice{"alice"};
+
583 Account const bob{"bob"};
+
584 const char credType[] = "abcde";
+
585
+
586 env.fund(XRP(5000), issuer, alice, bob);
+
587 env.close();
+
588
+
589 {
+
590 // Setup Bob with DepositAuth
+
591 env(fset(bob, asfDepositAuth));
+
592 env.close();
+
593 env(deposit::authCredentials(bob, {{issuer, credType}}));
+
594 env.close();
+
595 }
+
596
+
597 {
+
598 // Succeed
+
599 Json::Value jvParams;
+
600 jvParams[jss::ledger_index] = jss::validated;
+
601 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
602
+
603 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
605 auto& arr(
+
606 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
607
+
608 Json::Value jo;
+
609 jo[jss::issuer] = issuer.human();
+
610 jo[jss::credential_type] = strHex(std::string_view(credType));
+
611 arr.append(std::move(jo));
+
612 auto const jrr =
+
613 env.rpc("json", "ledger_entry", to_string(jvParams));
+
614
+
615 BEAST_EXPECT(
+
616 jrr.isObject() && jrr.isMember(jss::result) &&
+
617 !jrr[jss::result].isMember(jss::error) &&
+
618 jrr[jss::result].isMember(jss::node) &&
+
619 jrr[jss::result][jss::node].isMember(
+
620 sfLedgerEntryType.jsonName) &&
+
621 jrr[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
+
622 jss::DepositPreauth);
+
623 }
+
624
+
625 {
+
626 // Failed, invalid account
+
627 Json::Value jvParams;
+
628 jvParams[jss::ledger_index] = jss::validated;
+
629 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
630
+
631 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
633 auto& arr(
+
634 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
635
+
636 Json::Value jo;
+
637 jo[jss::issuer] = to_string(xrpAccount());
+
638 jo[jss::credential_type] = strHex(std::string_view(credType));
+
639 arr.append(std::move(jo));
+
640 auto const jrr =
+
641 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
643 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
644 }
+
645
+
646 {
+
647 // Failed, duplicates in credentials
+
648 Json::Value jvParams;
+
649 jvParams[jss::ledger_index] = jss::validated;
+
650 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
651
+
652 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
654 auto& arr(
+
655 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
656
+
657 Json::Value jo;
+
658 jo[jss::issuer] = issuer.human();
+
659 jo[jss::credential_type] = strHex(std::string_view(credType));
+
660 arr.append(jo);
+
661 arr.append(std::move(jo));
+
662 auto const jrr =
+
663 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
665 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
666 }
+
667
+
668 {
+
669 // Failed, invalid credential_type
+
670 Json::Value jvParams;
+
671 jvParams[jss::ledger_index] = jss::validated;
+
672 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
673
+
674 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
676 auto& arr(
+
677 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
678
+
679 Json::Value jo;
+
680 jo[jss::issuer] = issuer.human();
+
681 jo[jss::credential_type] = "";
+
682 arr.append(std::move(jo));
+
683
+
684 auto const jrr =
+
685 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
687 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
688 }
+
689
+
690 {
+
691 // Failed, authorized and authorized_credentials both present
+
692 Json::Value jvParams;
+
693 jvParams[jss::ledger_index] = jss::validated;
+
694 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
695 jvParams[jss::deposit_preauth][jss::authorized] = alice.human();
+
696
+
697 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
699 auto& arr(
+
700 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
701
+
702 Json::Value jo;
+
703 jo[jss::issuer] = issuer.human();
+
704 jo[jss::credential_type] = strHex(std::string_view(credType));
+
705 arr.append(std::move(jo));
+
706
+
707 auto const jrr =
+
708 env.rpc("json", "ledger_entry", to_string(jvParams));
+
709 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
710 }
+
711
+
712 {
+
713 // Failed, authorized_credentials is not an array
+
714 Json::Value jvParams;
+
715 jvParams[jss::ledger_index] = jss::validated;
+
716 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
717 jvParams[jss::deposit_preauth][jss::authorized_credentials] = 42;
+
718
+
719 auto const jrr =
+
720 env.rpc("json", "ledger_entry", to_string(jvParams));
+
721 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
722 }
+
723
+
724 {
+
725 // Failed, authorized_credentials contains string data
+
726 Json::Value jvParams;
+
727 jvParams[jss::ledger_index] = jss::validated;
+
728 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
729 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
731 auto& arr(
+
732 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
733 arr.append("foobar");
+
734
+
735 auto const jrr =
+
736 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
738 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
739 }
+
740
+
741 {
+
742 // Failed, authorized_credentials contains arrays
+
743 Json::Value jvParams;
+
744 jvParams[jss::ledger_index] = jss::validated;
+
745 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
746 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
748 auto& arr(
+
749 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+ +
751 payload.append(42);
+
752 arr.append(std::move(payload));
+
753
+
754 auto const jrr =
+
755 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
757 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
758 }
+
759
+
760 {
+
761 // Failed, authorized_credentials is empty array
+
762 Json::Value jvParams;
+
763 jvParams[jss::ledger_index] = jss::validated;
+
764 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
765 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
767
+
768 auto const jrr =
+
769 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
771 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
772 }
+
773
+
774 {
+
775 // Failed, authorized_credentials is too long
+
776
+
777 static const std::string_view credTypes[] = {
+
778 "cred1",
+
779 "cred2",
+
780 "cred3",
+
781 "cred4",
+
782 "cred5",
+
783 "cred6",
+
784 "cred7",
+
785 "cred8",
+
786 "cred9"};
+
787 static_assert(
+
788 sizeof(credTypes) / sizeof(credTypes[0]) >
+ +
790
+
791 Json::Value jvParams;
+
792 jvParams[jss::ledger_index] = jss::validated;
+
793 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
794 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
796
+
797 auto& arr(
+
798 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
799
+
800 for (unsigned i = 0; i < sizeof(credTypes) / sizeof(credTypes[0]);
+
801 ++i)
+
802 {
+
803 Json::Value jo;
+
804 jo[jss::issuer] = issuer.human();
+
805 jo[jss::credential_type] =
+
806 strHex(std::string_view(credTypes[i]));
+
807 arr.append(std::move(jo));
+
808 }
+
809
+
810 auto const jrr =
+
811 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
813 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
814 }
+
815
+
816 {
+
817 // Failed, issuer is not set
+
818 Json::Value jvParams;
+
819 jvParams[jss::ledger_index] = jss::validated;
+
820 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
821
+
822 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
824 auto& arr(
+
825 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
826
+
827 Json::Value jo;
+
828 jo[jss::credential_type] = strHex(std::string_view(credType));
+
829 arr.append(std::move(jo));
+
830
+
831 auto const jrr =
+
832 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
834 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
835 }
+
836
+
837 {
+
838 // Failed, issuer isn't string
+
839 Json::Value jvParams;
+
840 jvParams[jss::ledger_index] = jss::validated;
+
841 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
842
+
843 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
845 auto& arr(
+
846 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
847
+
848 Json::Value jo;
+
849 jo[jss::issuer] = 42;
+
850 jo[jss::credential_type] = strHex(std::string_view(credType));
+
851 arr.append(std::move(jo));
+
852
+
853 auto const jrr =
+
854 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
856 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
857 }
+
858
+
859 {
+
860 // Failed, issuer is an array
+
861 Json::Value jvParams;
+
862 jvParams[jss::ledger_index] = jss::validated;
+
863 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
864
+
865 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
867 auto& arr(
+
868 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
869
+
870 Json::Value jo;
+ +
872 payload.append(42);
+
873 jo[jss::issuer] = std::move(payload);
+
874 jo[jss::credential_type] = strHex(std::string_view(credType));
+
875 arr.append(std::move(jo));
+
876
+
877 auto const jrr =
+
878 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
880 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
881 }
+
882
+
883 {
+
884 // Failed, issuer isn't valid encoded account
+
885 Json::Value jvParams;
+
886 jvParams[jss::ledger_index] = jss::validated;
+
887 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
888
+
889 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
891 auto& arr(
+
892 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
893
+
894 Json::Value jo;
+
895 jo[jss::issuer] = "invalid_account";
+
896 jo[jss::credential_type] = strHex(std::string_view(credType));
+
897 arr.append(std::move(jo));
+
898
+
899 auto const jrr =
+
900 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
902 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
903 }
+
904
+
905 {
+
906 // Failed, credential_type is not set
+
907 Json::Value jvParams;
+
908 jvParams[jss::ledger_index] = jss::validated;
+
909 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
910
+
911 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
913 auto& arr(
+
914 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
915
+
916 Json::Value jo;
+
917 jo[jss::issuer] = issuer.human();
+
918 arr.append(std::move(jo));
+
919
+
920 auto const jrr =
+
921 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
923 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
924 }
+
925
+
926 {
+
927 // Failed, credential_type isn't string
+
928 Json::Value jvParams;
+
929 jvParams[jss::ledger_index] = jss::validated;
+
930 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
931
+
932 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
934 auto& arr(
+
935 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
936
+
937 Json::Value jo;
+
938 jo[jss::issuer] = issuer.human();
+
939 jo[jss::credential_type] = 42;
+
940 arr.append(std::move(jo));
+
941
+
942 auto const jrr =
+
943 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
945 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
946 }
+
947
+
948 {
+
949 // Failed, credential_type is an array
+
950 Json::Value jvParams;
+
951 jvParams[jss::ledger_index] = jss::validated;
+
952 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
953
+
954 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
956 auto& arr(
+
957 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
958
+
959 Json::Value jo;
+
960 jo[jss::issuer] = issuer.human();
+ +
962 payload.append(42);
+
963 jo[jss::credential_type] = std::move(payload);
+
964 arr.append(std::move(jo));
+
965
+
966 auto const jrr =
+
967 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
969 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
970 }
+
971
+
972 {
+
973 // Failed, credential_type isn't hex encoded
+
974 Json::Value jvParams;
+
975 jvParams[jss::ledger_index] = jss::validated;
+
976 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
+
977
+
978 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
+ +
980 auto& arr(
+
981 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
+
982
+
983 Json::Value jo;
+
984 jo[jss::issuer] = issuer.human();
+
985 jo[jss::credential_type] = "12KK";
+
986 arr.append(std::move(jo));
+
987
+
988 auto const jrr =
+
989 env.rpc("json", "ledger_entry", to_string(jvParams));
+ +
991 jrr[jss::result], "malformedAuthorizedCredentials", "");
+
992 }
+
993 }
+
994
+
995 void
+ +
997 {
+
998 testcase("ledger_entry Request Directory");
+
999 using namespace test::jtx;
+
1000 Env env{*this};
+
1001 Account const alice{"alice"};
+
1002 Account const gw{"gateway"};
+
1003 auto const USD = gw["USD"];
+
1004 env.fund(XRP(10000), alice, gw);
+
1005 env.close();
+
1006
+
1007 env.trust(USD(1000), alice);
+
1008 env.close();
+
1009
+
1010 // Run up the number of directory entries so alice has two
+
1011 // directory nodes.
+
1012 for (int d = 1'000'032; d >= 1'000'000; --d)
+
1013 {
+
1014 env(offer(alice, USD(1), drops(d)));
+
1015 }
+
1016 env.close();
+
1017
+
1018 std::string const ledgerHash{to_string(env.closed()->info().hash)};
+
1019 {
+
1020 // Exercise ledger_closed along the way.
+
1021 Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
+
1022 BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
+
1023 BEAST_EXPECT(jrr[jss::ledger_index] == 5);
+
1024 }
+
1025
+
1026 std::string const dirRootIndex =
+
1027 "A33EC6BB85FB5674074C4A3A43373BB17645308F3EAE1933E3E35252162B217D";
+
1028 {
+
1029 // Locate directory by index.
+
1030 Json::Value jvParams;
+
1031 jvParams[jss::directory] = dirRootIndex;
+
1032 jvParams[jss::ledger_hash] = ledgerHash;
+
1033 Json::Value const jrr = env.rpc(
+
1034 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1035 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 32);
+
1036 }
+
1037 {
+
1038 // Locate directory by directory root.
+
1039 Json::Value jvParams;
+
1040 jvParams[jss::directory] = Json::objectValue;
+
1041 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
+
1042 Json::Value const jrr = env.rpc(
+
1043 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1044 BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
+
1045 }
+
1046 {
+
1047 // Locate directory by owner.
+
1048 Json::Value jvParams;
+
1049 jvParams[jss::directory] = Json::objectValue;
+
1050 jvParams[jss::directory][jss::owner] = alice.human();
+
1051 jvParams[jss::ledger_hash] = ledgerHash;
+
1052 Json::Value const jrr = env.rpc(
+
1053 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1054 BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
+
1055 }
+
1056 {
+
1057 // Locate directory by directory root and sub_index.
+
1058 Json::Value jvParams;
+
1059 jvParams[jss::directory] = Json::objectValue;
+
1060 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
+
1061 jvParams[jss::directory][jss::sub_index] = 1;
+
1062 Json::Value const jrr = env.rpc(
+
1063 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1064 BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
+
1065 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
+
1066 }
+
1067 {
+
1068 // Locate directory by owner and sub_index.
+
1069 Json::Value jvParams;
+
1070 jvParams[jss::directory] = Json::objectValue;
+
1071 jvParams[jss::directory][jss::owner] = alice.human();
+
1072 jvParams[jss::directory][jss::sub_index] = 1;
+
1073 jvParams[jss::ledger_hash] = ledgerHash;
+
1074 Json::Value const jrr = env.rpc(
+
1075 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1076 BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
+
1077 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
+
1078 }
+
1079 {
+
1080 // Null directory argument.
+
1081 Json::Value jvParams;
+
1082 jvParams[jss::directory] = Json::nullValue;
+
1083 jvParams[jss::ledger_hash] = ledgerHash;
+
1084 Json::Value const jrr = env.rpc(
+
1085 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1086 checkErrorValue(jrr, "malformedRequest", "");
+
1087 }
+
1088 {
+
1089 // Non-integer sub_index.
+
1090 Json::Value jvParams;
+
1091 jvParams[jss::directory] = Json::objectValue;
+
1092 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
+
1093 jvParams[jss::directory][jss::sub_index] = 1.5;
+
1094 jvParams[jss::ledger_hash] = ledgerHash;
+
1095 Json::Value const jrr = env.rpc(
+
1096 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1097 checkErrorValue(jrr, "malformedRequest", "");
+
1098 }
+
1099 {
+
1100 // Malformed owner entry.
+
1101 Json::Value jvParams;
+
1102 jvParams[jss::directory] = Json::objectValue;
+
1103
+
1104 std::string const badAddress = makeBadAddress(alice.human());
+
1105 jvParams[jss::directory][jss::owner] = badAddress;
+
1106 jvParams[jss::ledger_hash] = ledgerHash;
+
1107 Json::Value const jrr = env.rpc(
+
1108 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1109 checkErrorValue(jrr, "malformedAddress", "");
+
1110 }
+
1111 {
+
1112 // Malformed directory object. Specify both dir_root and owner.
+
1113 Json::Value jvParams;
+
1114 jvParams[jss::directory] = Json::objectValue;
+
1115 jvParams[jss::directory][jss::owner] = alice.human();
+
1116 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
+
1117 jvParams[jss::ledger_hash] = ledgerHash;
+
1118 Json::Value const jrr = env.rpc(
+
1119 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1120 checkErrorValue(jrr, "malformedRequest", "");
+
1121 }
+
1122 {
+
1123 // Incomplete directory object. Missing both dir_root and owner.
+
1124 Json::Value jvParams;
+
1125 jvParams[jss::directory] = Json::objectValue;
+
1126 jvParams[jss::directory][jss::sub_index] = 1;
+
1127 jvParams[jss::ledger_hash] = ledgerHash;
+
1128 Json::Value const jrr = env.rpc(
+
1129 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1130 checkErrorValue(jrr, "malformedRequest", "");
+
1131 }
+
1132 }
+
1133
+
1134 void
+ +
1136 {
+
1137 testcase("ledger_entry Request Escrow");
+
1138 using namespace test::jtx;
+
1139 Env env{*this};
+
1140 Account const alice{"alice"};
+
1141 env.fund(XRP(10000), alice);
+
1142 env.close();
+
1143
+
1144 // Lambda to create an escrow.
+
1145 auto escrowCreate = [](test::jtx::Account const& account,
+
1146 test::jtx::Account const& to,
+
1147 STAmount const& amount,
+
1148 NetClock::time_point const& cancelAfter) {
+
1149 Json::Value jv;
+
1150 jv[jss::TransactionType] = jss::EscrowCreate;
+
1151 jv[jss::Flags] = tfUniversal;
+
1152 jv[jss::Account] = account.human();
+
1153 jv[jss::Destination] = to.human();
+
1154 jv[jss::Amount] = amount.getJson(JsonOptions::none);
+
1155 jv[sfFinishAfter.jsonName] =
+
1156 cancelAfter.time_since_epoch().count() + 2;
+
1157 return jv;
+
1158 };
+
1159
+
1160 using namespace std::chrono_literals;
+
1161 env(escrowCreate(alice, alice, XRP(333), env.now() + 2s));
+
1162 env.close();
+
1163
+
1164 std::string const ledgerHash{to_string(env.closed()->info().hash)};
+
1165 std::string escrowIndex;
+
1166 {
+
1167 // Request the escrow using owner and sequence.
+
1168 Json::Value jvParams;
+
1169 jvParams[jss::escrow] = Json::objectValue;
+
1170 jvParams[jss::escrow][jss::owner] = alice.human();
+
1171 jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1;
+
1172 Json::Value const jrr = env.rpc(
+
1173 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1174 BEAST_EXPECT(
+
1175 jrr[jss::node][jss::Amount] == XRP(333).value().getText());
+
1176 escrowIndex = jrr[jss::index].asString();
+
1177 }
+
1178 {
+
1179 // Request the escrow by index.
+
1180 Json::Value jvParams;
+
1181 jvParams[jss::escrow] = escrowIndex;
+
1182 jvParams[jss::ledger_hash] = ledgerHash;
+
1183 Json::Value const jrr = env.rpc(
+
1184 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1185 BEAST_EXPECT(
+
1186 jrr[jss::node][jss::Amount] == XRP(333).value().getText());
+
1187 }
+
1188 {
+
1189 // Malformed owner entry.
+
1190 Json::Value jvParams;
+
1191 jvParams[jss::escrow] = Json::objectValue;
+
1192
+
1193 std::string const badAddress = makeBadAddress(alice.human());
+
1194 jvParams[jss::escrow][jss::owner] = badAddress;
+
1195 jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1;
+
1196 jvParams[jss::ledger_hash] = ledgerHash;
+
1197 Json::Value const jrr = env.rpc(
+
1198 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1199 checkErrorValue(jrr, "malformedOwner", "");
+
1200 }
+
1201 {
+
1202 // Missing owner.
+
1203 Json::Value jvParams;
+
1204 jvParams[jss::escrow] = Json::objectValue;
+
1205 jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1;
+
1206 jvParams[jss::ledger_hash] = ledgerHash;
+
1207 Json::Value const jrr = env.rpc(
+
1208 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1209 checkErrorValue(jrr, "malformedRequest", "");
+
1210 }
+
1211 {
+
1212 // Missing sequence.
+
1213 Json::Value jvParams;
+
1214 jvParams[jss::escrow] = Json::objectValue;
+
1215 jvParams[jss::escrow][jss::owner] = alice.human();
+
1216 jvParams[jss::ledger_hash] = ledgerHash;
+
1217 Json::Value const jrr = env.rpc(
+
1218 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1219 checkErrorValue(jrr, "malformedRequest", "");
+
1220 }
+
1221 {
+
1222 // Non-integer sequence.
+
1223 Json::Value jvParams;
+
1224 jvParams[jss::escrow] = Json::objectValue;
+
1225 jvParams[jss::escrow][jss::owner] = alice.human();
+
1226 jvParams[jss::escrow][jss::seq] =
+
1227 std::to_string(env.seq(alice) - 1);
+
1228 jvParams[jss::ledger_hash] = ledgerHash;
+
1229 Json::Value const jrr = env.rpc(
+
1230 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1231 checkErrorValue(jrr, "malformedRequest", "");
+
1232 }
+
1233 }
+
1234
+
1235 void
+ +
1237 {
+
1238 testcase("ledger_entry Request Offer");
+
1239 using namespace test::jtx;
+
1240 Env env{*this};
+
1241 Account const alice{"alice"};
+
1242 Account const gw{"gateway"};
+
1243 auto const USD = gw["USD"];
+
1244 env.fund(XRP(10000), alice, gw);
+
1245 env.close();
+
1246
+
1247 env(offer(alice, USD(321), XRP(322)));
+
1248 env.close();
+
1249
+
1250 std::string const ledgerHash{to_string(env.closed()->info().hash)};
+
1251 std::string offerIndex;
+
1252 {
+
1253 // Request the offer using owner and sequence.
+
1254 Json::Value jvParams;
+
1255 jvParams[jss::offer] = Json::objectValue;
+
1256 jvParams[jss::offer][jss::account] = alice.human();
+
1257 jvParams[jss::offer][jss::seq] = env.seq(alice) - 1;
+
1258 jvParams[jss::ledger_hash] = ledgerHash;
+
1259 Json::Value const jrr = env.rpc(
+
1260 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1261 BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
+
1262 offerIndex = jrr[jss::index].asString();
+
1263 }
+
1264 {
+
1265 // Request the offer using its index.
+
1266 Json::Value jvParams;
+
1267 jvParams[jss::offer] = offerIndex;
+
1268 Json::Value const jrr = env.rpc(
+
1269 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1270 BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
+
1271 }
+
1272 {
+
1273 // Malformed account entry.
+
1274 Json::Value jvParams;
+
1275 jvParams[jss::offer] = Json::objectValue;
+
1276
+
1277 std::string const badAddress = makeBadAddress(alice.human());
+
1278 jvParams[jss::offer][jss::account] = badAddress;
+
1279 jvParams[jss::offer][jss::seq] = env.seq(alice) - 1;
+
1280 jvParams[jss::ledger_hash] = ledgerHash;
+
1281 Json::Value const jrr = env.rpc(
+
1282 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1283 checkErrorValue(jrr, "malformedAddress", "");
+
1284 }
+
1285 {
+
1286 // Malformed offer object. Missing account member.
+
1287 Json::Value jvParams;
+
1288 jvParams[jss::offer] = Json::objectValue;
+
1289 jvParams[jss::offer][jss::seq] = env.seq(alice) - 1;
+
1290 jvParams[jss::ledger_hash] = ledgerHash;
+
1291 Json::Value const jrr = env.rpc(
+
1292 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1293 checkErrorValue(jrr, "malformedRequest", "");
+
1294 }
+
1295 {
+
1296 // Malformed offer object. Missing seq member.
+
1297 Json::Value jvParams;
+
1298 jvParams[jss::offer] = Json::objectValue;
+
1299 jvParams[jss::offer][jss::account] = alice.human();
+
1300 jvParams[jss::ledger_hash] = ledgerHash;
+
1301 Json::Value const jrr = env.rpc(
+
1302 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1303 checkErrorValue(jrr, "malformedRequest", "");
+
1304 }
+
1305 {
+
1306 // Malformed offer object. Non-integral seq member.
+
1307 Json::Value jvParams;
+
1308 jvParams[jss::offer] = Json::objectValue;
+
1309 jvParams[jss::offer][jss::account] = alice.human();
+
1310 jvParams[jss::offer][jss::seq] = std::to_string(env.seq(alice) - 1);
+
1311 jvParams[jss::ledger_hash] = ledgerHash;
+
1312 Json::Value const jrr = env.rpc(
+
1313 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1314 checkErrorValue(jrr, "malformedRequest", "");
+
1315 }
+
1316 }
+
1317
+
1318 void
+ +
1320 {
+
1321 testcase("ledger_entry Request Pay Chan");
+
1322 using namespace test::jtx;
+
1323 using namespace std::literals::chrono_literals;
+
1324 Env env{*this};
+
1325 Account const alice{"alice"};
+
1326
+
1327 env.fund(XRP(10000), alice);
+
1328 env.close();
+
1329
+
1330 // Lambda to create a PayChan.
+
1331 auto payChanCreate = [](test::jtx::Account const& account,
+
1332 test::jtx::Account const& to,
+
1333 STAmount const& amount,
+
1334 NetClock::duration const& settleDelay,
+
1335 PublicKey const& pk) {
+
1336 Json::Value jv;
+
1337 jv[jss::TransactionType] = jss::PaymentChannelCreate;
+
1338 jv[jss::Account] = account.human();
+
1339 jv[jss::Destination] = to.human();
+
1340 jv[jss::Amount] = amount.getJson(JsonOptions::none);
+
1341 jv[sfSettleDelay.jsonName] = settleDelay.count();
+
1342 jv[sfPublicKey.jsonName] = strHex(pk.slice());
+
1343 return jv;
+
1344 };
+
1345
+
1346 env(payChanCreate(alice, env.master, XRP(57), 18s, alice.pk()));
+
1347 env.close();
+
1348
+
1349 std::string const ledgerHash{to_string(env.closed()->info().hash)};
+
1350
+
1351 uint256 const payChanIndex{
+
1352 keylet::payChan(alice, env.master, env.seq(alice) - 1).key};
+
1353 {
+
1354 // Request the payment channel using its index.
+
1355 Json::Value jvParams;
+
1356 jvParams[jss::payment_channel] = to_string(payChanIndex);
+
1357 jvParams[jss::ledger_hash] = ledgerHash;
+
1358 Json::Value const jrr = env.rpc(
+
1359 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1360 BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "57000000");
+
1361 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "0");
+
1362 BEAST_EXPECT(jrr[jss::node][sfSettleDelay.jsonName] == 18);
+
1363 }
+
1364 {
+
1365 // Request an index that is not a payment channel.
+
1366 Json::Value jvParams;
+
1367 jvParams[jss::payment_channel] = ledgerHash;
+
1368 jvParams[jss::ledger_hash] = ledgerHash;
+
1369 Json::Value const jrr = env.rpc(
+
1370 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1371 checkErrorValue(jrr, "entryNotFound", "");
+
1372 }
+
1373 }
+
1374
+
1375 void
+ +
1377 {
+
1378 testcase("ledger_entry Request RippleState");
+
1379 using namespace test::jtx;
+
1380 Env env{*this};
+
1381 Account const alice{"alice"};
+
1382 Account const gw{"gateway"};
+
1383 auto const USD = gw["USD"];
+
1384 env.fund(XRP(10000), alice, gw);
+
1385 env.close();
+
1386
+
1387 env.trust(USD(999), alice);
+
1388 env.close();
+
1389
+
1390 env(pay(gw, alice, USD(97)));
+
1391 env.close();
+
1392
+
1393 // check both aliases
+
1394 for (auto const& fieldName : {jss::ripple_state, jss::state})
+
1395 {
+
1396 std::string const ledgerHash{to_string(env.closed()->info().hash)};
+
1397 {
+
1398 // Request the trust line using the accounts and currency.
+
1399 Json::Value jvParams;
+
1400 jvParams[fieldName] = Json::objectValue;
+
1401 jvParams[fieldName][jss::accounts] = Json::arrayValue;
+
1402 jvParams[fieldName][jss::accounts][0u] = alice.human();
+
1403 jvParams[fieldName][jss::accounts][1u] = gw.human();
+
1404 jvParams[fieldName][jss::currency] = "USD";
+
1405 jvParams[jss::ledger_hash] = ledgerHash;
+
1406 Json::Value const jrr = env.rpc(
+
1407 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1408 BEAST_EXPECT(
+
1409 jrr[jss::node][sfBalance.jsonName][jss::value] == "-97");
+
1410 BEAST_EXPECT(
+
1411 jrr[jss::node][sfHighLimit.jsonName][jss::value] == "999");
+
1412 }
+
1413 {
+
1414 // ripple_state is not an object.
+
1415 Json::Value jvParams;
+
1416 jvParams[fieldName] = "ripple_state";
+
1417 jvParams[jss::ledger_hash] = ledgerHash;
+
1418 Json::Value const jrr = env.rpc(
+
1419 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1420 checkErrorValue(jrr, "malformedRequest", "");
+
1421 }
+
1422 {
+
1423 // ripple_state.currency is missing.
+
1424 Json::Value jvParams;
+
1425 jvParams[fieldName] = Json::objectValue;
+
1426 jvParams[fieldName][jss::accounts] = Json::arrayValue;
+
1427 jvParams[fieldName][jss::accounts][0u] = alice.human();
+
1428 jvParams[fieldName][jss::accounts][1u] = gw.human();
+
1429 jvParams[jss::ledger_hash] = ledgerHash;
+
1430 Json::Value const jrr = env.rpc(
+
1431 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1432 checkErrorValue(jrr, "malformedRequest", "");
+
1433 }
+
1434 {
+
1435 // ripple_state accounts is not an array.
+
1436 Json::Value jvParams;
+
1437 jvParams[fieldName] = Json::objectValue;
+
1438 jvParams[fieldName][jss::accounts] = 2;
+
1439 jvParams[fieldName][jss::currency] = "USD";
+
1440 jvParams[jss::ledger_hash] = ledgerHash;
+
1441 Json::Value const jrr = env.rpc(
+
1442 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1443 checkErrorValue(jrr, "malformedRequest", "");
+
1444 }
+
1445 {
+
1446 // ripple_state one of the accounts is missing.
+
1447 Json::Value jvParams;
+
1448 jvParams[fieldName] = Json::objectValue;
+
1449 jvParams[fieldName][jss::accounts] = Json::arrayValue;
+
1450 jvParams[fieldName][jss::accounts][0u] = alice.human();
+
1451 jvParams[fieldName][jss::currency] = "USD";
+
1452 jvParams[jss::ledger_hash] = ledgerHash;
+
1453 Json::Value const jrr = env.rpc(
+
1454 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1455 checkErrorValue(jrr, "malformedRequest", "");
+
1456 }
+
1457 {
+
1458 // ripple_state more than 2 accounts.
+
1459 Json::Value jvParams;
+
1460 jvParams[fieldName] = Json::objectValue;
+
1461 jvParams[fieldName][jss::accounts] = Json::arrayValue;
+
1462 jvParams[fieldName][jss::accounts][0u] = alice.human();
+
1463 jvParams[fieldName][jss::accounts][1u] = gw.human();
+
1464 jvParams[fieldName][jss::accounts][2u] = alice.human();
+
1465 jvParams[fieldName][jss::currency] = "USD";
+
1466 jvParams[jss::ledger_hash] = ledgerHash;
+
1467 Json::Value const jrr = env.rpc(
+
1468 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1469 checkErrorValue(jrr, "malformedRequest", "");
+
1470 }
+
1471 {
+
1472 // ripple_state account[0] is not a string.
+
1473 Json::Value jvParams;
+
1474 jvParams[fieldName] = Json::objectValue;
+
1475 jvParams[fieldName][jss::accounts] = Json::arrayValue;
+
1476 jvParams[fieldName][jss::accounts][0u] = 44;
+
1477 jvParams[fieldName][jss::accounts][1u] = gw.human();
+
1478 jvParams[fieldName][jss::currency] = "USD";
+
1479 jvParams[jss::ledger_hash] = ledgerHash;
+
1480 Json::Value const jrr = env.rpc(
+
1481 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1482 checkErrorValue(jrr, "malformedRequest", "");
+
1483 }
+
1484 {
+
1485 // ripple_state account[1] is not a string.
+
1486 Json::Value jvParams;
+
1487 jvParams[fieldName] = Json::objectValue;
+
1488 jvParams[fieldName][jss::accounts] = Json::arrayValue;
+
1489 jvParams[fieldName][jss::accounts][0u] = alice.human();
+
1490 jvParams[fieldName][jss::accounts][1u] = 21;
+
1491 jvParams[fieldName][jss::currency] = "USD";
+
1492 jvParams[jss::ledger_hash] = ledgerHash;
+
1493 Json::Value const jrr = env.rpc(
+
1494 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1495 checkErrorValue(jrr, "malformedRequest", "");
+
1496 }
+
1497 {
+
1498 // ripple_state account[0] == account[1].
+
1499 Json::Value jvParams;
+
1500 jvParams[fieldName] = Json::objectValue;
+
1501 jvParams[fieldName][jss::accounts] = Json::arrayValue;
+
1502 jvParams[fieldName][jss::accounts][0u] = alice.human();
+
1503 jvParams[fieldName][jss::accounts][1u] = alice.human();
+
1504 jvParams[fieldName][jss::currency] = "USD";
+
1505 jvParams[jss::ledger_hash] = ledgerHash;
+
1506 Json::Value const jrr = env.rpc(
+
1507 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1508 checkErrorValue(jrr, "malformedRequest", "");
+
1509 }
+
1510 {
+
1511 // ripple_state malformed account[0].
+
1512 Json::Value jvParams;
+
1513 jvParams[fieldName] = Json::objectValue;
+
1514 jvParams[fieldName][jss::accounts] = Json::arrayValue;
+
1515 jvParams[fieldName][jss::accounts][0u] =
+
1516 makeBadAddress(alice.human());
+
1517 jvParams[fieldName][jss::accounts][1u] = gw.human();
+
1518 jvParams[fieldName][jss::currency] = "USD";
+
1519 jvParams[jss::ledger_hash] = ledgerHash;
+
1520 Json::Value const jrr = env.rpc(
+
1521 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1522 checkErrorValue(jrr, "malformedAddress", "");
+
1523 }
+
1524 {
+
1525 // ripple_state malformed account[1].
+
1526 Json::Value jvParams;
+
1527 jvParams[fieldName] = Json::objectValue;
+
1528 jvParams[fieldName][jss::accounts] = Json::arrayValue;
+
1529 jvParams[fieldName][jss::accounts][0u] = alice.human();
+
1530 jvParams[fieldName][jss::accounts][1u] =
+
1531 makeBadAddress(gw.human());
+
1532 jvParams[fieldName][jss::currency] = "USD";
+
1533 jvParams[jss::ledger_hash] = ledgerHash;
+
1534 Json::Value const jrr = env.rpc(
+
1535 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1536 checkErrorValue(jrr, "malformedAddress", "");
+
1537 }
+
1538 {
+
1539 // ripple_state malformed currency.
+
1540 Json::Value jvParams;
+
1541 jvParams[fieldName] = Json::objectValue;
+
1542 jvParams[fieldName][jss::accounts] = Json::arrayValue;
+
1543 jvParams[fieldName][jss::accounts][0u] = alice.human();
+
1544 jvParams[fieldName][jss::accounts][1u] = gw.human();
+
1545 jvParams[fieldName][jss::currency] = "USDollars";
+
1546 jvParams[jss::ledger_hash] = ledgerHash;
+
1547 Json::Value const jrr = env.rpc(
+
1548 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1549 checkErrorValue(jrr, "malformedCurrency", "");
+
1550 }
+
1551 }
+
1552 }
+
1553
+
1554 void
+ +
1556 {
+
1557 testcase("ledger_entry Request Ticket");
+
1558 using namespace test::jtx;
+
1559 Env env{*this};
+
1560 env.close();
+
1561
+
1562 // Create two tickets.
+
1563 std::uint32_t const tkt1{env.seq(env.master) + 1};
+
1564 env(ticket::create(env.master, 2));
+
1565 env.close();
+
1566
+
1567 std::string const ledgerHash{to_string(env.closed()->info().hash)};
+
1568 // Request four tickets: one before the first one we created, the
+
1569 // two created tickets, and the ticket that would come after the
+
1570 // last created ticket.
+
1571 {
+
1572 // Not a valid ticket requested by index.
+
1573 Json::Value jvParams;
+
1574 jvParams[jss::ticket] =
+
1575 to_string(getTicketIndex(env.master, tkt1 - 1));
+
1576 jvParams[jss::ledger_hash] = ledgerHash;
+
1577 Json::Value const jrr = env.rpc(
+
1578 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1579 checkErrorValue(jrr, "entryNotFound", "");
+
1580 }
+
1581 {
+
1582 // First real ticket requested by index.
+
1583 Json::Value jvParams;
+
1584 jvParams[jss::ticket] = to_string(getTicketIndex(env.master, tkt1));
+
1585 jvParams[jss::ledger_hash] = ledgerHash;
+
1586 Json::Value const jrr = env.rpc(
+
1587 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1588 BEAST_EXPECT(
+
1589 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Ticket);
+
1590 BEAST_EXPECT(jrr[jss::node][sfTicketSequence.jsonName] == tkt1);
+
1591 }
+
1592 {
+
1593 // Second real ticket requested by account and sequence.
+
1594 Json::Value jvParams;
+
1595 jvParams[jss::ticket] = Json::objectValue;
+
1596 jvParams[jss::ticket][jss::account] = env.master.human();
+
1597 jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 1;
+
1598 jvParams[jss::ledger_hash] = ledgerHash;
+
1599 Json::Value const jrr = env.rpc(
+
1600 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1601 BEAST_EXPECT(
+
1602 jrr[jss::node][jss::index] ==
+
1603 to_string(getTicketIndex(env.master, tkt1 + 1)));
+
1604 }
+
1605 {
+
1606 // Not a valid ticket requested by account and sequence.
+
1607 Json::Value jvParams;
+
1608 jvParams[jss::ticket] = Json::objectValue;
+
1609 jvParams[jss::ticket][jss::account] = env.master.human();
+
1610 jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 2;
+
1611 jvParams[jss::ledger_hash] = ledgerHash;
+
1612 Json::Value const jrr = env.rpc(
+
1613 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1614 checkErrorValue(jrr, "entryNotFound", "");
+
1615 }
+
1616 {
+
1617 // Request a ticket using an account root entry.
+
1618 Json::Value jvParams;
+
1619 jvParams[jss::ticket] = to_string(keylet::account(env.master).key);
+
1620 jvParams[jss::ledger_hash] = ledgerHash;
+
1621 Json::Value const jrr = env.rpc(
+
1622 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1623 checkErrorValue(jrr, "unexpectedLedgerType", "");
+
1624 }
+
1625 {
+
1626 // Malformed account entry.
+
1627 Json::Value jvParams;
+
1628 jvParams[jss::ticket] = Json::objectValue;
+
1629
+
1630 std::string const badAddress = makeBadAddress(env.master.human());
+
1631 jvParams[jss::ticket][jss::account] = badAddress;
+
1632 jvParams[jss::ticket][jss::ticket_seq] = env.seq(env.master) - 1;
+
1633 jvParams[jss::ledger_hash] = ledgerHash;
+
1634 Json::Value const jrr = env.rpc(
+
1635 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1636 checkErrorValue(jrr, "malformedAddress", "");
+
1637 }
+
1638 {
+
1639 // Malformed ticket object. Missing account member.
+
1640 Json::Value jvParams;
+
1641 jvParams[jss::ticket] = Json::objectValue;
+
1642 jvParams[jss::ticket][jss::ticket_seq] = env.seq(env.master) - 1;
+
1643 jvParams[jss::ledger_hash] = ledgerHash;
+
1644 Json::Value const jrr = env.rpc(
+
1645 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1646 checkErrorValue(jrr, "malformedRequest", "");
+
1647 }
+
1648 {
+
1649 // Malformed ticket object. Missing seq member.
+
1650 Json::Value jvParams;
+
1651 jvParams[jss::ticket] = Json::objectValue;
+
1652 jvParams[jss::ticket][jss::account] = env.master.human();
+
1653 jvParams[jss::ledger_hash] = ledgerHash;
+
1654 Json::Value const jrr = env.rpc(
+
1655 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1656 checkErrorValue(jrr, "malformedRequest", "");
+
1657 }
+
1658 {
+
1659 // Malformed ticket object. Non-integral seq member.
+
1660 Json::Value jvParams;
+
1661 jvParams[jss::ticket] = Json::objectValue;
+
1662 jvParams[jss::ticket][jss::account] = env.master.human();
+
1663 jvParams[jss::ticket][jss::ticket_seq] =
+
1664 std::to_string(env.seq(env.master) - 1);
+
1665 jvParams[jss::ledger_hash] = ledgerHash;
+
1666 Json::Value const jrr = env.rpc(
+
1667 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1668 checkErrorValue(jrr, "malformedRequest", "");
+
1669 }
+
1670 }
+
1671
+
1672 void
+ +
1674 {
+
1675 testcase("ledger_entry Request DID");
+
1676 using namespace test::jtx;
+
1677 using namespace std::literals::chrono_literals;
+
1678 Env env{*this};
+
1679 Account const alice{"alice"};
+
1680
+
1681 env.fund(XRP(10000), alice);
+
1682 env.close();
+
1683
+
1684 // Lambda to create a DID.
+
1685 auto didCreate = [](test::jtx::Account const& account) {
+
1686 Json::Value jv;
+
1687 jv[jss::TransactionType] = jss::DIDSet;
+
1688 jv[jss::Account] = account.human();
+
1689 jv[sfDIDDocument.jsonName] = strHex(std::string{"data"});
+
1690 jv[sfURI.jsonName] = strHex(std::string{"uri"});
+
1691 return jv;
+
1692 };
+
1693
+
1694 env(didCreate(alice));
+
1695 env.close();
+
1696
+
1697 std::string const ledgerHash{to_string(env.closed()->info().hash)};
+
1698
+
1699 {
+
1700 // Request the DID using its index.
+
1701 Json::Value jvParams;
+
1702 jvParams[jss::did] = alice.human();
+
1703 jvParams[jss::ledger_hash] = ledgerHash;
+
1704 Json::Value const jrr = env.rpc(
+
1705 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1706 BEAST_EXPECT(
+
1707 jrr[jss::node][sfDIDDocument.jsonName] ==
+
1708 strHex(std::string{"data"}));
+
1709 BEAST_EXPECT(
+
1710 jrr[jss::node][sfURI.jsonName] == strHex(std::string{"uri"}));
+
1711 }
+
1712 {
+
1713 // Request an index that is not a DID.
+
1714 Json::Value jvParams;
+
1715 jvParams[jss::did] = env.master.human();
+
1716 jvParams[jss::ledger_hash] = ledgerHash;
+
1717 Json::Value const jrr = env.rpc(
+
1718 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1719 checkErrorValue(jrr, "entryNotFound", "");
+
1720 }
+
1721 }
+
1722
+
1723 void
+
1724 testLedgerEntryInvalidParams(unsigned int apiVersion)
+
1725 {
+
1726 testcase(
+
1727 "ledger_entry Request With Invalid Parameters v" +
+
1728 std::to_string(apiVersion));
+
1729 using namespace test::jtx;
+
1730 Env env{*this};
+
1731
+
1732 std::string const ledgerHash{to_string(env.closed()->info().hash)};
+
1733
+
1734 auto makeParams = [&apiVersion](std::function<void(Json::Value&)> f) {
+
1735 Json::Value params;
+
1736 params[jss::api_version] = apiVersion;
+
1737 f(params);
+
1738 return params;
+
1739 };
+
1740 // "features" is not an option supported by ledger_entry.
+
1741 {
+
1742 auto const jvParams =
+
1743 makeParams([&ledgerHash](Json::Value& jvParams) {
+
1744 jvParams[jss::features] = ledgerHash;
+
1745 jvParams[jss::ledger_hash] = ledgerHash;
+
1746 });
+
1747 Json::Value const jrr = env.rpc(
+
1748 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1749
+
1750 if (apiVersion < 2u)
+
1751 checkErrorValue(jrr, "unknownOption", "");
+
1752 else
+
1753 checkErrorValue(jrr, "invalidParams", "");
+
1754 }
+
1755 Json::Value const injectObject = []() {
+ +
1757 obj[jss::account] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
+
1758 obj[jss::ledger_index] = "validated";
+
1759 return obj;
+
1760 }();
+
1761 Json::Value const injectArray = []() {
+ +
1763 arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
+
1764 arr[1u] = "validated";
+
1765 return arr;
+
1766 }();
+
1767
+
1768 // invalid input for fields that can handle an object, but can't handle
+
1769 // an array
+
1770 for (auto const& field :
+
1771 {jss::directory, jss::escrow, jss::offer, jss::ticket, jss::amm})
+
1772 {
+
1773 auto const jvParams =
+
1774 makeParams([&field, &injectArray](Json::Value& jvParams) {
+
1775 jvParams[field] = injectArray;
+
1776 });
+
1777
+
1778 Json::Value const jrr = env.rpc(
+
1779 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1780
+
1781 if (apiVersion < 2u)
+
1782 checkErrorValue(jrr, "internal", "Internal error.");
+
1783 else
+
1784 checkErrorValue(jrr, "invalidParams", "");
+
1785 }
+
1786 // Fields that can handle objects just fine
+
1787 for (auto const& field :
+
1788 {jss::directory, jss::escrow, jss::offer, jss::ticket, jss::amm})
+
1789 {
+
1790 auto const jvParams =
+
1791 makeParams([&field, &injectObject](Json::Value& jvParams) {
+
1792 jvParams[field] = injectObject;
+
1793 });
+
1794
+
1795 Json::Value const jrr = env.rpc(
+
1796 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1797
+
1798 checkErrorValue(jrr, "malformedRequest", "");
+
1799 }
+
1800
+
1801 for (auto const& inject : {injectObject, injectArray})
+
1802 {
+
1803 // invalid input for fields that can't handle an object or an array
+
1804 for (auto const& field :
+
1805 {jss::index,
+
1806 jss::account_root,
+
1807 jss::check,
+
1808 jss::payment_channel})
+
1809 {
+
1810 auto const jvParams =
+
1811 makeParams([&field, &inject](Json::Value& jvParams) {
+
1812 jvParams[field] = inject;
+
1813 });
+
1814
+
1815 Json::Value const jrr = env.rpc(
+
1816 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1817
+
1818 if (apiVersion < 2u)
+
1819 checkErrorValue(jrr, "internal", "Internal error.");
+
1820 else
+
1821 checkErrorValue(jrr, "invalidParams", "");
+
1822 }
+
1823 // directory sub-fields
+
1824 for (auto const& field : {jss::dir_root, jss::owner})
+
1825 {
+
1826 auto const jvParams =
+
1827 makeParams([&field, &inject](Json::Value& jvParams) {
+
1828 jvParams[jss::directory][field] = inject;
+
1829 });
+
1830
+
1831 Json::Value const jrr = env.rpc(
+
1832 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1833
+
1834 if (apiVersion < 2u)
+
1835 checkErrorValue(jrr, "internal", "Internal error.");
+
1836 else
+
1837 checkErrorValue(jrr, "invalidParams", "");
+
1838 }
+
1839 // escrow sub-fields
+
1840 {
+
1841 auto const jvParams =
+
1842 makeParams([&inject](Json::Value& jvParams) {
+
1843 jvParams[jss::escrow][jss::owner] = inject;
+
1844 jvParams[jss::escrow][jss::seq] = 99;
+
1845 });
+
1846
+
1847 Json::Value const jrr = env.rpc(
+
1848 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1849
+
1850 if (apiVersion < 2u)
+
1851 checkErrorValue(jrr, "internal", "Internal error.");
+
1852 else
+
1853 checkErrorValue(jrr, "invalidParams", "");
+
1854 }
+
1855 // offer sub-fields
+
1856 {
+
1857 auto const jvParams =
+
1858 makeParams([&inject](Json::Value& jvParams) {
+
1859 jvParams[jss::offer][jss::account] = inject;
+
1860 jvParams[jss::offer][jss::seq] = 99;
+
1861 });
+
1862
+
1863 Json::Value const jrr = env.rpc(
+
1864 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1865
+
1866 if (apiVersion < 2u)
+
1867 checkErrorValue(jrr, "internal", "Internal error.");
+
1868 else
+
1869 checkErrorValue(jrr, "invalidParams", "");
+
1870 }
+
1871 // ripple_state sub-fields
+
1872 {
+
1873 auto const jvParams =
+
1874 makeParams([&inject](Json::Value& jvParams) {
+ +
1876 rs[jss::currency] = "FOO";
+
1877 rs[jss::accounts] = Json::Value(Json::arrayValue);
+
1878 rs[jss::accounts][0u] =
+
1879 "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
+
1880 rs[jss::accounts][1u] =
+
1881 "rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv";
+
1882 rs[jss::currency] = inject;
+
1883 jvParams[jss::ripple_state] = std::move(rs);
+
1884 });
+
1885
+
1886 Json::Value const jrr = env.rpc(
+
1887 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1888
+
1889 if (apiVersion < 2u)
+
1890 checkErrorValue(jrr, "internal", "Internal error.");
+
1891 else
+
1892 checkErrorValue(jrr, "invalidParams", "");
+
1893 }
+
1894 // ticket sub-fields
+
1895 {
+
1896 auto const jvParams =
+
1897 makeParams([&inject](Json::Value& jvParams) {
+
1898 jvParams[jss::ticket][jss::account] = inject;
+
1899 jvParams[jss::ticket][jss::ticket_seq] = 99;
+
1900 });
+
1901
+
1902 Json::Value const jrr = env.rpc(
+
1903 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1904
+
1905 if (apiVersion < 2u)
+
1906 checkErrorValue(jrr, "internal", "Internal error.");
+
1907 else
+
1908 checkErrorValue(jrr, "invalidParams", "");
+
1909 }
+
1910
+
1911 // Fields that can handle malformed inputs just fine
+
1912 for (auto const& field : {jss::nft_page, jss::deposit_preauth})
+
1913 {
+
1914 auto const jvParams =
+
1915 makeParams([&field, &inject](Json::Value& jvParams) {
+
1916 jvParams[field] = inject;
+
1917 });
+
1918
+
1919 Json::Value const jrr = env.rpc(
+
1920 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1921
+
1922 checkErrorValue(jrr, "malformedRequest", "");
+
1923 }
+
1924 // Subfields of deposit_preauth that can handle malformed inputs
+
1925 // fine
+
1926 for (auto const& field : {jss::owner, jss::authorized})
+
1927 {
+
1928 auto const jvParams =
+
1929 makeParams([&field, &inject](Json::Value& jvParams) {
+
1930 auto pa = Json::Value(Json::objectValue);
+
1931 pa[jss::owner] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
+
1932 pa[jss::authorized] =
+
1933 "rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv";
+
1934 pa[field] = inject;
+
1935 jvParams[jss::deposit_preauth] = std::move(pa);
+
1936 });
+
1937
+
1938 Json::Value const jrr = env.rpc(
+
1939 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
1940
+
1941 checkErrorValue(jrr, "malformedRequest", "");
+
1942 }
+
1943 }
+
1944 }
+
1945
+
1946 void
+ +
1948 {
+
1949 testcase("Invalid Oracle Ledger Entry");
+
1950 using namespace ripple::test::jtx;
+
1951 using namespace ripple::test::jtx::oracle;
+
1952
+
1953 Env env(*this);
+
1954 Account const owner("owner");
+
1955 env.fund(XRP(1'000), owner);
+
1956 Oracle oracle(
+
1957 env,
+
1958 {.owner = owner,
+
1959 .fee = static_cast<int>(env.current()->fees().base.drops())});
+
1960
+
1961 // Malformed document id
+
1962 auto res = Oracle::ledgerEntry(env, owner, NoneTag);
+
1963 BEAST_EXPECT(res[jss::error].asString() == "invalidParams");
+
1964 std::vector<AnyValue> invalid = {-1, 1.2, "", "Invalid"};
+
1965 for (auto const& v : invalid)
+
1966 {
+
1967 auto const res = Oracle::ledgerEntry(env, owner, v);
+
1968 BEAST_EXPECT(res[jss::error].asString() == "malformedDocumentID");
+
1969 }
+
1970 // Missing document id
+
1971 res = Oracle::ledgerEntry(env, owner, std::nullopt);
+
1972 BEAST_EXPECT(res[jss::error].asString() == "malformedRequest");
+
1973
+
1974 // Missing account
+
1975 res = Oracle::ledgerEntry(env, std::nullopt, 1);
+
1976 BEAST_EXPECT(res[jss::error].asString() == "malformedRequest");
+
1977
+
1978 // Malformed account
+
1979 std::string malfAccount = to_string(owner.id());
+
1980 malfAccount.replace(10, 1, 1, '!');
+
1981 res = Oracle::ledgerEntry(env, malfAccount, 1);
+
1982 BEAST_EXPECT(res[jss::error].asString() == "malformedAddress");
+
1983 }
+
1984
+
1985 void
+ +
1987 {
+
1988 testcase("Oracle Ledger Entry");
+
1989 using namespace ripple::test::jtx;
+
1990 using namespace ripple::test::jtx::oracle;
+
1991
+
1992 Env env(*this);
+
1993 auto const baseFee =
+
1994 static_cast<int>(env.current()->fees().base.drops());
+
1995 std::vector<AccountID> accounts;
+ +
1997 for (int i = 0; i < 10; ++i)
+
1998 {
+
1999 Account const owner(std::string("owner") + std::to_string(i));
+
2000 env.fund(XRP(1'000), owner);
+
2001 // different accounts can have the same asset pair
+
2002 Oracle oracle(
+
2003 env, {.owner = owner, .documentID = i, .fee = baseFee});
+
2004 accounts.push_back(owner.id());
+
2005 oracles.push_back(oracle.documentID());
+
2006 // same account can have different asset pair
+
2007 Oracle oracle1(
+
2008 env, {.owner = owner, .documentID = i + 10, .fee = baseFee});
+
2009 accounts.push_back(owner.id());
+
2010 oracles.push_back(oracle1.documentID());
+
2011 }
+
2012 for (int i = 0; i < accounts.size(); ++i)
+
2013 {
+
2014 auto const jv = [&]() {
+
2015 // document id is uint32
+
2016 if (i % 2)
+
2017 return Oracle::ledgerEntry(env, accounts[i], oracles[i]);
+
2018 // document id is string
+
2019 return Oracle::ledgerEntry(
+
2020 env, accounts[i], std::to_string(oracles[i]));
+
2021 }();
+
2022 try
+
2023 {
+
2024 BEAST_EXPECT(
+
2025 jv[jss::node][jss::Owner] == to_string(accounts[i]));
+
2026 }
+
2027 catch (...)
+
2028 {
+
2029 fail();
+
2030 }
+
2031 }
+
2032 }
+
2033
+
2034 void
+ +
2036 {
+
2037 testcase("ledger_entry Request MPT");
+
2038 using namespace test::jtx;
+
2039 using namespace std::literals::chrono_literals;
+
2040 Env env{*this};
+
2041 Account const alice{"alice"};
+
2042 Account const bob("bob");
+
2043
+
2044 MPTTester mptAlice(env, alice, {.holders = {bob}});
+
2045 mptAlice.create(
+
2046 {.transferFee = 10,
+
2047 .metadata = "123",
+
2048 .ownerCount = 1,
+ + +
2051 mptAlice.authorize({.account = bob, .holderCount = 1});
+
2052
+
2053 std::string const ledgerHash{to_string(env.closed()->info().hash)};
+
2054
+
2055 std::string const badMptID =
+
2056 "00000193B9DDCAF401B5B3B26875986043F82CD0D13B4315";
+
2057 {
+
2058 // Request the MPTIssuance using its MPTIssuanceID.
+
2059 Json::Value jvParams;
+
2060 jvParams[jss::mpt_issuance] = strHex(mptAlice.issuanceID());
+
2061 jvParams[jss::ledger_hash] = ledgerHash;
+
2062 Json::Value const jrr = env.rpc(
+
2063 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
2064 BEAST_EXPECT(
+
2065 jrr[jss::node][sfMPTokenMetadata.jsonName] ==
+
2066 strHex(std::string{"123"}));
+
2067 BEAST_EXPECT(
+
2068 jrr[jss::node][jss::mpt_issuance_id] ==
+
2069 strHex(mptAlice.issuanceID()));
+
2070 }
+
2071 {
+
2072 // Request an index that is not a MPTIssuance.
+
2073 Json::Value jvParams;
+
2074 jvParams[jss::mpt_issuance] = badMptID;
+
2075 jvParams[jss::ledger_hash] = ledgerHash;
+
2076 Json::Value const jrr = env.rpc(
+
2077 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
2078 checkErrorValue(jrr, "entryNotFound", "");
+
2079 }
+
2080 {
+
2081 // Request the MPToken using its owner + mptIssuanceID.
+
2082 Json::Value jvParams;
+
2083 jvParams[jss::mptoken] = Json::objectValue;
+
2084 jvParams[jss::mptoken][jss::account] = bob.human();
+
2085 jvParams[jss::mptoken][jss::mpt_issuance_id] =
+
2086 strHex(mptAlice.issuanceID());
+
2087 jvParams[jss::ledger_hash] = ledgerHash;
+
2088 Json::Value const jrr = env.rpc(
+
2089 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
2090 BEAST_EXPECT(
+
2091 jrr[jss::node][sfMPTokenIssuanceID.jsonName] ==
+
2092 strHex(mptAlice.issuanceID()));
+
2093 }
+
2094 {
+
2095 // Request the MPToken using a bad mptIssuanceID.
+
2096 Json::Value jvParams;
+
2097 jvParams[jss::mptoken] = Json::objectValue;
+
2098 jvParams[jss::mptoken][jss::account] = bob.human();
+
2099 jvParams[jss::mptoken][jss::mpt_issuance_id] = badMptID;
+
2100 jvParams[jss::ledger_hash] = ledgerHash;
+
2101 Json::Value const jrr = env.rpc(
+
2102 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
2103 checkErrorValue(jrr, "entryNotFound", "");
+
2104 }
+
2105 }
+
2106
+
2107 void
+ +
2109 {
+
2110 testcase("ledger_entry PermissionedDomain");
+
2111
+
2112 using namespace test::jtx;
+
2113
+
2114 Env env(*this, supported_amendments() | featurePermissionedDomains);
+
2115 Account const issuer{"issuer"};
+
2116 Account const alice{"alice"};
+
2117 Account const bob{"bob"};
+
2118
+
2119 env.fund(XRP(5000), issuer, alice, bob);
+
2120 env.close();
+
2121
+
2122 auto const seq = env.seq(alice);
+
2123 env(pdomain::setTx(alice, {{alice, "first credential"}}));
+
2124 env.close();
+
2125 auto const objects = pdomain::getObjects(alice, env);
+
2126 if (!BEAST_EXPECT(objects.size() == 1))
+
2127 return;
+
2128
+
2129 {
+
2130 // Succeed
+
2131 Json::Value params;
+
2132 params[jss::ledger_index] = jss::validated;
+
2133 params[jss::permissioned_domain][jss::account] = alice.human();
+
2134 params[jss::permissioned_domain][jss::seq] = seq;
+
2135 auto jv = env.rpc("json", "ledger_entry", to_string(params));
+
2136 BEAST_EXPECT(
+
2137 jv.isObject() && jv.isMember(jss::result) &&
+
2138 !jv[jss::result].isMember(jss::error) &&
+
2139 jv[jss::result].isMember(jss::node) &&
+
2140 jv[jss::result][jss::node].isMember(
+
2141 sfLedgerEntryType.jsonName) &&
+
2142 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
+
2143 jss::PermissionedDomain);
+
2144
+
2145 std::string const pdIdx = jv[jss::result][jss::index].asString();
+
2146 BEAST_EXPECT(
+
2147 strHex(keylet::permissionedDomain(alice, seq).key) == pdIdx);
+
2148
+
2149 params.clear();
+
2150 params[jss::ledger_index] = jss::validated;
+
2151 params[jss::permissioned_domain] = pdIdx;
+
2152 jv = env.rpc("json", "ledger_entry", to_string(params));
+
2153 BEAST_EXPECT(
+
2154 jv.isObject() && jv.isMember(jss::result) &&
+
2155 !jv[jss::result].isMember(jss::error) &&
+
2156 jv[jss::result].isMember(jss::node) &&
+
2157 jv[jss::result][jss::node].isMember(
+
2158 sfLedgerEntryType.jsonName) &&
+
2159 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
+
2160 jss::PermissionedDomain);
+
2161 }
+
2162
+
2163 {
+
2164 // Fail, invalid permissioned domain index
+
2165 Json::Value params;
+
2166 params[jss::ledger_index] = jss::validated;
+
2167 params[jss::permissioned_domain] =
+
2168 "12F1F1F1F180D67377B2FAB292A31C922470326268D2B9B74CD1E582645B9A"
+
2169 "DE";
+
2170 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
+
2171 checkErrorValue(jrr[jss::result], "entryNotFound", "");
+
2172 }
+
2173
+
2174 {
+
2175 // Fail, invalid permissioned domain index
+
2176 Json::Value params;
+
2177 params[jss::ledger_index] = jss::validated;
+
2178 params[jss::permissioned_domain] = "NotAHexString";
+
2179 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
+
2180 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
2181 }
+
2182
+
2183 {
+
2184 // Fail, permissioned domain is not an object
+
2185 Json::Value params;
+
2186 params[jss::ledger_index] = jss::validated;
+
2187 params[jss::permissioned_domain] = 10;
+
2188 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
+
2189 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
2190 }
+
2191
+
2192 {
+
2193 // Fail, invalid account
+
2194 Json::Value params;
+
2195 params[jss::ledger_index] = jss::validated;
+
2196 params[jss::permissioned_domain][jss::account] = 1;
+
2197 params[jss::permissioned_domain][jss::seq] = seq;
+
2198 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
+
2199 checkErrorValue(jrr[jss::result], "malformedAddress", "");
+
2200 }
+
2201
+
2202 {
+
2203 // Fail, account is an object
+
2204 Json::Value params;
+
2205 params[jss::ledger_index] = jss::validated;
+
2206 params[jss::permissioned_domain][jss::account] =
+ +
2208 params[jss::permissioned_domain][jss::seq] = seq;
+
2209 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
+
2210 checkErrorValue(jrr[jss::result], "malformedAddress", "");
+
2211 }
+
2212
+
2213 {
+
2214 // Fail, no account
+
2215 Json::Value params;
+
2216 params[jss::ledger_index] = jss::validated;
+
2217 params[jss::permissioned_domain][jss::account] = "";
+
2218 params[jss::permissioned_domain][jss::seq] = seq;
+
2219 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
+
2220 checkErrorValue(jrr[jss::result], "malformedAddress", "");
+
2221 }
+
2222
+
2223 {
+
2224 // Fail, invalid sequence
+
2225 Json::Value params;
+
2226 params[jss::ledger_index] = jss::validated;
+
2227 params[jss::permissioned_domain][jss::account] = alice.human();
+
2228 params[jss::permissioned_domain][jss::seq] = "12g";
+
2229 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
+
2230 checkErrorValue(jrr[jss::result], "malformedRequest", "");
+
2231 }
+
2232 }
+
2233
+
2234 void
+ +
2236 {
+
2237 testcase("ledger_entry command-line");
+
2238 using namespace test::jtx;
+
2239
+
2240 Env env{*this};
+
2241 Account const alice{"alice"};
+
2242 env.fund(XRP(10000), alice);
+
2243 env.close();
+
2244
+
2245 auto const checkId = keylet::check(env.master, env.seq(env.master));
+
2246
+
2247 env(check::create(env.master, alice, XRP(100)));
+
2248 env.close();
+
2249
+
2250 std::string const ledgerHash{to_string(env.closed()->info().hash)};
+
2251 {
+
2252 // Request a check.
+
2253 Json::Value const jrr =
+
2254 env.rpc("ledger_entry", to_string(checkId.key))[jss::result];
+
2255 BEAST_EXPECT(
+
2256 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
+
2257 BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
+
2258 }
+
2259 }
+
2260
+
2261public:
+
2262 void
+
2263 run() override
+
2264 {
+ + + + + + + + + + + + + + + + + + +
2283
+ + +
2286 }
+
2287};
+
2288
+ + +
2291{
+
2292 void
+ +
2294 Json::Value const& jv,
+
2295 std::string const& err,
+
2296 std::string const& msg)
+
2297 {
+
2298 if (BEAST_EXPECT(jv.isMember(jss::status)))
+
2299 BEAST_EXPECT(jv[jss::status] == "error");
+
2300 if (BEAST_EXPECT(jv.isMember(jss::error)))
+
2301 BEAST_EXPECT(jv[jss::error] == err);
+
2302 if (msg.empty())
+
2303 {
+
2304 BEAST_EXPECT(
+
2305 jv[jss::error_message] == Json::nullValue ||
+
2306 jv[jss::error_message] == "");
+
2307 }
+
2308 else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
+
2309 BEAST_EXPECT(jv[jss::error_message] == msg);
+
2310 }
+
2311
+
2312 void
+ +
2314 {
+
2315 testcase("ledger_entry: bridge");
+
2316 using namespace test::jtx;
+
2317
+
2318 Env mcEnv{*this, features};
+
2319 Env scEnv(*this, envconfig(), features);
+
2320
+
2321 createBridgeObjects(mcEnv, scEnv);
+
2322
+
2323 std::string const ledgerHash{to_string(mcEnv.closed()->info().hash)};
+
2324 std::string bridge_index;
+
2325 Json::Value mcBridge;
+
2326 {
+
2327 // request the bridge via RPC
+
2328 Json::Value jvParams;
+
2329 jvParams[jss::bridge_account] = mcDoor.human();
+
2330 jvParams[jss::bridge] = jvb;
+
2331 Json::Value const jrr = mcEnv.rpc(
+
2332 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
2333
+
2334 BEAST_EXPECT(jrr.isMember(jss::node));
+
2335 auto r = jrr[jss::node];
+
2336 // std::cout << to_string(r) << '\n';
+
2337
+
2338 BEAST_EXPECT(r.isMember(jss::Account));
+
2339 BEAST_EXPECT(r[jss::Account] == mcDoor.human());
+
2340
+
2341 BEAST_EXPECT(r.isMember(jss::Flags));
+
2342
+
2343 BEAST_EXPECT(r.isMember(sfLedgerEntryType.jsonName));
+
2344 BEAST_EXPECT(r[sfLedgerEntryType.jsonName] == jss::Bridge);
+
2345
+
2346 // we not created an account yet
+
2347 BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
+
2348 BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 0);
+
2349
+
2350 // we have not claimed a locking chain tx yet
+
2351 BEAST_EXPECT(r.isMember(sfXChainAccountClaimCount.jsonName));
+
2352 BEAST_EXPECT(r[sfXChainAccountClaimCount.jsonName].asInt() == 0);
+
2353
+
2354 BEAST_EXPECT(r.isMember(jss::index));
+
2355 bridge_index = r[jss::index].asString();
+
2356 mcBridge = r;
+
2357 }
+
2358 {
+
2359 // request the bridge via RPC by index
+
2360 Json::Value jvParams;
+
2361 jvParams[jss::index] = bridge_index;
+
2362 Json::Value const jrr = mcEnv.rpc(
+
2363 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
2364
+
2365 BEAST_EXPECT(jrr.isMember(jss::node));
+
2366 BEAST_EXPECT(jrr[jss::node] == mcBridge);
+
2367 }
+
2368 {
+
2369 // swap door accounts and make sure we get an error value
+
2370 Json::Value jvParams;
+
2371 // Sidechain door account is "master", not scDoor
+
2372 jvParams[jss::bridge_account] = Account::master.human();
+
2373 jvParams[jss::bridge] = jvb;
+
2374 jvParams[jss::ledger_hash] = ledgerHash;
+
2375 Json::Value const jrr = mcEnv.rpc(
+
2376 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
2377
+
2378 checkErrorValue(jrr, "entryNotFound", "");
+
2379 }
+
2380 {
+
2381 // create two claim ids and verify that the bridge counter was
+
2382 // incremented
+ +
2384 mcEnv.close();
+ +
2386 mcEnv.close();
+
2387
+
2388 // request the bridge via RPC
+
2389 Json::Value jvParams;
+
2390 jvParams[jss::bridge_account] = mcDoor.human();
+
2391 jvParams[jss::bridge] = jvb;
+
2392 // std::cout << to_string(jvParams) << '\n';
+
2393 Json::Value const jrr = mcEnv.rpc(
+
2394 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
2395
+
2396 BEAST_EXPECT(jrr.isMember(jss::node));
+
2397 auto r = jrr[jss::node];
+
2398
+
2399 // we executed two create claim id txs
+
2400 BEAST_EXPECT(r.isMember(sfXChainClaimID.jsonName));
+
2401 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
+
2402 }
+
2403 }
+
2404
+
2405 void
+ +
2407 {
+
2408 testcase("ledger_entry: xchain_claim_id");
+
2409 using namespace test::jtx;
+
2410
+
2411 Env mcEnv{*this, features};
+
2412 Env scEnv(*this, envconfig(), features);
+
2413
+
2414 createBridgeObjects(mcEnv, scEnv);
+
2415
+ +
2417 scEnv.close();
+ +
2419 scEnv.close();
+
2420
+
2421 std::string bridge_index;
+
2422 {
+
2423 // request the xchain_claim_id via RPC
+
2424 Json::Value jvParams;
+
2425 jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
+
2426 jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
+
2427 1;
+
2428 // std::cout << to_string(jvParams) << '\n';
+
2429 Json::Value const jrr = scEnv.rpc(
+
2430 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
2431
+
2432 BEAST_EXPECT(jrr.isMember(jss::node));
+
2433 auto r = jrr[jss::node];
+
2434 // std::cout << to_string(r) << '\n';
+
2435
+
2436 BEAST_EXPECT(r.isMember(jss::Account));
+
2437 BEAST_EXPECT(r[jss::Account] == scAlice.human());
+
2438 BEAST_EXPECT(
+
2439 r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
+
2440 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 1);
+
2441 BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
+
2442 }
+
2443
+
2444 {
+
2445 // request the xchain_claim_id via RPC
+
2446 Json::Value jvParams;
+
2447 jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
+
2448 jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
+
2449 2;
+
2450 Json::Value const jrr = scEnv.rpc(
+
2451 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
2452
+
2453 BEAST_EXPECT(jrr.isMember(jss::node));
+
2454 auto r = jrr[jss::node];
+
2455 // std::cout << to_string(r) << '\n';
+
2456
+
2457 BEAST_EXPECT(r.isMember(jss::Account));
+
2458 BEAST_EXPECT(r[jss::Account] == scBob.human());
+
2459 BEAST_EXPECT(
+
2460 r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
+
2461 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
+
2462 BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
+
2463 }
+
2464 }
+
2465
+
2466 void
+ +
2468 {
+
2469 testcase("ledger_entry: xchain_create_account_claim_id");
+
2470 using namespace test::jtx;
+
2471
+
2472 Env mcEnv{*this, features};
+
2473 Env scEnv(*this, envconfig(), features);
+
2474
+
2475 // note: signers.size() and quorum are both 5 in createBridgeObjects
+
2476 createBridgeObjects(mcEnv, scEnv);
+
2477
+
2478 auto scCarol =
+
2479 Account("scCarol"); // Don't fund it - it will be created with the
+
2480 // xchain transaction
+
2481 auto const amt = XRP(1000);
+ +
2483 mcAlice, jvb, scCarol, amt, reward));
+
2484 mcEnv.close();
+
2485
+
2486 // send less than quorum of attestations (otherwise funds are
+
2487 // immediately transferred and no "claim" object is created)
+
2488 size_t constexpr num_attest = 3;
+
2489 auto attestations = create_account_attestations(
+
2490 scAttester,
+
2491 jvb,
+
2492 mcAlice,
+
2493 amt,
+
2494 reward,
+
2495 payee,
+
2496 /*wasLockingChainSend*/ true,
+
2497 1,
+
2498 scCarol,
+
2499 signers,
+ +
2501 for (size_t i = 0; i < num_attest; ++i)
+
2502 {
+
2503 scEnv(attestations[i]);
+
2504 }
+
2505 scEnv.close();
+
2506
+
2507 {
+
2508 // request the create account claim_id via RPC
+
2509 Json::Value jvParams;
+
2510 jvParams[jss::xchain_owned_create_account_claim_id] =
+ +
2512 jvParams[jss::xchain_owned_create_account_claim_id]
+
2513 [jss::xchain_owned_create_account_claim_id] = 1;
+
2514 // std::cout << to_string(jvParams) << '\n';
+
2515 Json::Value const jrr = scEnv.rpc(
+
2516 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
2517 // std::cout << to_string(jrr) << '\n';
+
2518
+
2519 BEAST_EXPECT(jrr.isMember(jss::node));
+
2520 auto r = jrr[jss::node];
+
2521
+
2522 BEAST_EXPECT(r.isMember(jss::Account));
+
2523 BEAST_EXPECT(r[jss::Account] == Account::master.human());
+
2524
+
2525 BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
+
2526 BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 1);
+
2527
+
2528 BEAST_EXPECT(
+
2529 r.isMember(sfXChainCreateAccountAttestations.jsonName));
+
2530 auto attest = r[sfXChainCreateAccountAttestations.jsonName];
+
2531 BEAST_EXPECT(attest.isArray());
+
2532 BEAST_EXPECT(attest.size() == 3);
+
2533 BEAST_EXPECT(attest[Json::Value::UInt(0)].isMember(
+
2534 sfXChainCreateAccountProofSig.jsonName));
+
2535 Json::Value a[num_attest];
+
2536 for (size_t i = 0; i < num_attest; ++i)
+
2537 {
+
2538 a[i] = attest[Json::Value::UInt(0)]
+
2539 [sfXChainCreateAccountProofSig.jsonName];
+
2540 BEAST_EXPECT(
+
2541 a[i].isMember(jss::Amount) &&
+
2542 a[i][jss::Amount].asInt() == 1000 * drop_per_xrp);
+
2543 BEAST_EXPECT(
+
2544 a[i].isMember(jss::Destination) &&
+
2545 a[i][jss::Destination] == scCarol.human());
+
2546 BEAST_EXPECT(
+
2547 a[i].isMember(sfAttestationSignerAccount.jsonName) &&
+ +
2549 signers.begin(), signers.end(), [&](signer const& s) {
+
2550 return a[i][sfAttestationSignerAccount.jsonName] ==
+
2551 s.account.human();
+
2552 }));
+
2553 BEAST_EXPECT(
+
2554 a[i].isMember(sfAttestationRewardAccount.jsonName) &&
+ +
2556 payee.begin(),
+
2557 payee.end(),
+
2558 [&](Account const& account) {
+
2559 return a[i][sfAttestationRewardAccount.jsonName] ==
+
2560 account.human();
+
2561 }));
+
2562 BEAST_EXPECT(
+
2563 a[i].isMember(sfWasLockingChainSend.jsonName) &&
+
2564 a[i][sfWasLockingChainSend.jsonName] == 1);
+
2565 BEAST_EXPECT(
+
2566 a[i].isMember(sfSignatureReward.jsonName) &&
+
2567 a[i][sfSignatureReward.jsonName].asInt() ==
+
2568 1 * drop_per_xrp);
+
2569 }
+
2570 }
+
2571
+
2572 // complete attestations quorum - CreateAccountClaimID should not be
+
2573 // present anymore
+
2574 for (size_t i = num_attest; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS; ++i)
+
2575 {
+
2576 scEnv(attestations[i]);
+
2577 }
+
2578 scEnv.close();
+
2579 {
+
2580 // request the create account claim_id via RPC
+
2581 Json::Value jvParams;
+
2582 jvParams[jss::xchain_owned_create_account_claim_id] =
+ +
2584 jvParams[jss::xchain_owned_create_account_claim_id]
+
2585 [jss::xchain_owned_create_account_claim_id] = 1;
+
2586 // std::cout << to_string(jvParams) << '\n';
+
2587 Json::Value const jrr = scEnv.rpc(
+
2588 "json", "ledger_entry", to_string(jvParams))[jss::result];
+
2589 checkErrorValue(jrr, "entryNotFound", "");
+
2590 }
+
2591 }
+
2592
+
2593public:
+
2594 void
+
2595 run() override
+
2596 {
+ + + +
2600 }
+
2601};
+
2602
+
2603BEAST_DEFINE_TESTSUITE(LedgerEntry, app, ripple);
+
2604BEAST_DEFINE_TESTSUITE(LedgerEntry_XChain, app, ripple);
+
2605
+
2606} // namespace test
+
2607} // namespace ripple
+
T any_of(T... args)
+ + +
T bind_front(T... args)
+
Represents a JSON value.
Definition: json_value.h:148
+
Json::UInt UInt
Definition: json_value.h:155
+
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:712
+
void clear()
Remove all object members and array elements.
Definition: json_value.cpp:759
+
Int asInt() const
Definition: json_value.cpp:509
+
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:897
+
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:475
+
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:949
+
A testsuite class.
Definition: suite.h:55
+
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
+
A public key.
Definition: PublicKey.h:62
+ + + + + + +
void checkErrorValue(Json::Value const &jv, std::string const &err, std::string const &msg)
+
void run() override
Runs the suite.
+ + + + + + + + + + +
void testLedgerEntryInvalidParams(unsigned int apiVersion)
+ + + + +
void checkErrorValue(Json::Value const &jv, std::string const &err, std::string const &msg)
+ + +
std::string makeBadAddress(std::string good)
+
void run() override
Runs the suite.
+ + + +
Immutable cryptographic account descriptor.
Definition: Account.h:39
+
AccountID id() const
Returns the Account ID.
Definition: Account.h:107
+
static Account const master
The master account.
Definition: Account.h:48
+
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
+
A transaction testing environment.
Definition: Env.h:120
+
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:330
+
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:769
+
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
+ +
void create(MPTCreate const &arg=MPTCreate{})
Definition: mpt.cpp:86
+
Oracle class facilitates unit-testing of the Price Oracle feature.
Definition: Oracle.h:120
+ +
T empty(T... args)
+ + +
@ nullValue
'null' value
Definition: json_value.h:37
+
@ arrayValue
array value (ordered list)
Definition: json_value.h:43
+
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
+
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition: Indexes.cpp:547
+
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:175
+
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition: Indexes.cpp:327
+
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition: Indexes.cpp:386
+
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Definition: TestHelpers.h:441
+
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:32
+
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:83
+
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition: deposit.cpp:32
+
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition: deposit.cpp:54
+ +
Json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< uint256 > domain)
+
std::map< uint256, Json::Value > getObjects(Account const &account, Env &env, bool withType)
+
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition: ticket.cpp:31
+ +
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
+
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 sidechain_xchain_account_create(Account const &acc, Json::Value const &bridge, Account const &dst, AnyAmount const &amt, AnyAmount const &reward)
+
constexpr std::size_t UT_XCHAIN_DEFAULT_NUM_SIGNERS
Definition: xchain_bridge.h:37
+
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
+
JValueVec create_account_attestations(jtx::Account const &submittingAccount, Json::Value const &jvBridge, jtx::Account const &sendingAccount, jtx::AnyAmount const &sendingAmount, jtx::AnyAmount const &rewardAmount, std::vector< jtx::Account > const &rewardAccounts, bool wasLockingChainSend, std::uint64_t createCount, jtx::Account const &dst, std::vector< jtx::signer > const &signers, std::size_t const numAtts, std::size_t const fromIdx)
+
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
+
Json::Value xchain_create_claim_id(Account const &acc, Json::Value const &bridge, STAmount const &reward, Account const &otherChainSource)
+
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:29
+
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
+
FeatureBitset supported_amendments()
Definition: Env.h:73
+
static uint256 ledgerHash(LedgerInfo const &info)
+
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
+
constexpr std::uint32_t asfDepositAuth
Definition: TxFlags.h:84
+
AccountID const & xrpAccount()
Compute AccountID from public key.
Definition: AccountID.cpp:178
+
constexpr std::uint32_t const tfMPTCanTransfer
Definition: TxFlags.h:145
+
constexpr std::uint32_t const tfMPTCanTrade
Definition: TxFlags.h:144
+
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
+
std::size_t constexpr maxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition: Protocol.h:107
+
@ invalid
Timely, but invalid signature.
+
void forAllApiVersions(Fn const &fn, Args &&... args)
Definition: ApiVersion.h:102
+
constexpr std::uint32_t tfUniversal
Definition: TxFlags.h:61
+
uint256 getTicketIndex(AccountID const &account, std::uint32_t uSequence)
Definition: Indexes.cpp:147
+
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
+
constexpr std::uint32_t const tfMPTCanEscrow
Definition: TxFlags.h:143
+
constexpr std::uint32_t const tfMPTRequireAuth
Definition: TxFlags.h:142
+
constexpr std::uint32_t const tfMPTCanLock
Definition: TxFlags.h:141
+
constexpr std::uint32_t const tfMPTCanClawback
Definition: TxFlags.h:146
+
T push_back(T... args)
+
T replace(T... args)
+
T size(T... args)
+ +
uint256 key
Definition: Keylet.h:40
+ + + + + +
std::vector< Account > const payee
+ + + + + + + +
std::vector< signer > const signers
+
void createBridgeObjects(Env &mcEnv, Env &scEnv)
+ + +
Set the sequence number on a JTx.
Definition: seq.h:34
+
A signer in a SignerList.
Definition: multisign.h:38
+ +
T to_string(T... args)
+ +
+ + + + diff --git a/LedgerHistory__test_8cpp_source.html b/LedgerHistory__test_8cpp_source.html index b5fb98f9e1..cdb02af544 100644 --- a/LedgerHistory__test_8cpp_source.html +++ b/LedgerHistory__test_8cpp_source.html @@ -298,8 +298,8 @@ $(function() {
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:120
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:493
-
Application & app()
Definition: Env.h:258
+
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:495
+
Application & app()
Definition: Env.h:260
beast::Journal const journal
Definition: Env.h:161
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
diff --git a/LedgerRPC__test_8cpp_source.html b/LedgerRPC__test_8cpp_source.html index 7aeec658f8..e0ccaada69 100644 --- a/LedgerRPC__test_8cpp_source.html +++ b/LedgerRPC__test_8cpp_source.html @@ -105,17 +105,17 @@ $(function() {
27
28#include <xrpl/beast/unit_test.h>
29#include <xrpl/json/json_value.h>
-
30#include <xrpl/protocol/AccountID.h>
-
31#include <xrpl/protocol/ErrorCodes.h>
-
32#include <xrpl/protocol/jss.h>
-
33
-
34namespace ripple {
-
35
-
36class LedgerRPC_XChain_test : public beast::unit_test::suite,
-
37 public test::jtx::XChainBridgeObjects
+
30#include <xrpl/protocol/ErrorCodes.h>
+
31#include <xrpl/protocol/jss.h>
+
32
+
33namespace ripple {
+
34
+
35namespace test {
+
36
+
37class LedgerRPC_test : public beast::unit_test::suite
38{
39 void
-
40 checkErrorValue(
+
40 checkErrorValue(
41 Json::Value const& jv,
42 std::string const& err,
43 std::string const& msg)
@@ -134,3358 +134,759 @@ $(function() {
56 BEAST_EXPECT(jv[jss::error_message] == msg);
57 }
58
-
59 void
-
60 testLedgerEntryBridge()
-
61 {
-
62 testcase("ledger_entry: bridge");
-
63 using namespace test::jtx;
-
64
-
65 Env mcEnv{*this, features};
-
66 Env scEnv(*this, envconfig(), features);
-
67
-
68 createBridgeObjects(mcEnv, scEnv);
-
69
-
70 std::string const ledgerHash{to_string(mcEnv.closed()->info().hash)};
-
71 std::string bridge_index;
-
72 Json::Value mcBridge;
-
73 {
-
74 // request the bridge via RPC
-
75 Json::Value jvParams;
-
76 jvParams[jss::bridge_account] = mcDoor.human();
-
77 jvParams[jss::bridge] = jvb;
-
78 Json::Value const jrr = mcEnv.rpc(
-
79 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
80
-
81 BEAST_EXPECT(jrr.isMember(jss::node));
-
82 auto r = jrr[jss::node];
-
83 // std::cout << to_string(r) << '\n';
-
84
-
85 BEAST_EXPECT(r.isMember(jss::Account));
-
86 BEAST_EXPECT(r[jss::Account] == mcDoor.human());
-
87
-
88 BEAST_EXPECT(r.isMember(jss::Flags));
+
59 // Corrupt a valid address by replacing the 10th character with '!'.
+
60 // '!' is not part of the ripple alphabet.
+
61 std::string
+
62 makeBadAddress(std::string good)
+
63 {
+
64 std::string ret = std::move(good);
+
65 ret.replace(10, 1, 1, '!');
+
66 return ret;
+
67 }
+
68
+
69 void
+
70 testLedgerRequest()
+
71 {
+
72 testcase("Basic Request");
+
73 using namespace test::jtx;
+
74
+
75 Env env{*this};
+
76
+
77 env.close();
+
78 BEAST_EXPECT(env.current()->info().seq == 4);
+
79
+
80 {
+
81 Json::Value jvParams;
+
82 // can be either numeric or quoted numeric
+
83 jvParams[jss::ledger_index] = 1;
+
84 auto const jrr =
+
85 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
+
86 BEAST_EXPECT(jrr[jss::ledger][jss::closed] == true);
+
87 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "1");
+
88 }
89
-
90 BEAST_EXPECT(r.isMember(sfLedgerEntryType.jsonName));
-
91 BEAST_EXPECT(r[sfLedgerEntryType.jsonName] == jss::Bridge);
-
92
-
93 // we not created an account yet
-
94 BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
-
95 BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 0);
-
96
-
97 // we have not claimed a locking chain tx yet
-
98 BEAST_EXPECT(r.isMember(sfXChainAccountClaimCount.jsonName));
-
99 BEAST_EXPECT(r[sfXChainAccountClaimCount.jsonName].asInt() == 0);
-
100
-
101 BEAST_EXPECT(r.isMember(jss::index));
-
102 bridge_index = r[jss::index].asString();
-
103 mcBridge = r;
-
104 }
-
105 {
-
106 // request the bridge via RPC by index
-
107 Json::Value jvParams;
-
108 jvParams[jss::index] = bridge_index;
-
109 Json::Value const jrr = mcEnv.rpc(
-
110 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
111
-
112 BEAST_EXPECT(jrr.isMember(jss::node));
-
113 BEAST_EXPECT(jrr[jss::node] == mcBridge);
-
114 }
-
115 {
-
116 // swap door accounts and make sure we get an error value
-
117 Json::Value jvParams;
-
118 // Sidechain door account is "master", not scDoor
-
119 jvParams[jss::bridge_account] = Account::master.human();
-
120 jvParams[jss::bridge] = jvb;
-
121 jvParams[jss::ledger_hash] = ledgerHash;
-
122 Json::Value const jrr = mcEnv.rpc(
-
123 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
124
-
125 checkErrorValue(jrr, "entryNotFound", "");
-
126 }
-
127 {
-
128 // create two claim ids and verify that the bridge counter was
-
129 // incremented
-
130 mcEnv(xchain_create_claim_id(mcAlice, jvb, reward, scAlice));
-
131 mcEnv.close();
-
132 mcEnv(xchain_create_claim_id(mcBob, jvb, reward, scBob));
-
133 mcEnv.close();
+
90 {
+
91 Json::Value jvParams;
+
92 jvParams[jss::ledger_index] = "1";
+
93 auto const jrr =
+
94 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
+
95 BEAST_EXPECT(jrr[jss::ledger][jss::closed] == true);
+
96 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "1");
+
97 }
+
98
+
99 {
+
100 // using current identifier
+
101 auto const jrr = env.rpc("ledger", "current")[jss::result];
+
102 BEAST_EXPECT(jrr[jss::ledger][jss::closed] == false);
+
103 BEAST_EXPECT(
+
104 jrr[jss::ledger][jss::ledger_index] ==
+
105 std::to_string(env.current()->info().seq));
+
106 BEAST_EXPECT(
+
107 jrr[jss::ledger_current_index] == env.current()->info().seq);
+
108 }
+
109 }
+
110
+
111 void
+
112 testBadInput()
+
113 {
+
114 testcase("Bad Input");
+
115 using namespace test::jtx;
+
116 Env env{*this};
+
117 Account const gw{"gateway"};
+
118 auto const USD = gw["USD"];
+
119 Account const bob{"bob"};
+
120
+
121 env.fund(XRP(10000), gw, bob);
+
122 env.close();
+
123 env.trust(USD(1000), bob);
+
124 env.close();
+
125
+
126 {
+
127 // ask for an arbitrary string - index
+
128 Json::Value jvParams;
+
129 jvParams[jss::ledger_index] = "potato";
+
130 auto const jrr =
+
131 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
+
132 checkErrorValue(jrr, "invalidParams", "ledgerIndexMalformed");
+
133 }
134
-
135 // request the bridge via RPC
-
136 Json::Value jvParams;
-
137 jvParams[jss::bridge_account] = mcDoor.human();
-
138 jvParams[jss::bridge] = jvb;
-
139 // std::cout << to_string(jvParams) << '\n';
-
140 Json::Value const jrr = mcEnv.rpc(
-
141 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
142
-
143 BEAST_EXPECT(jrr.isMember(jss::node));
-
144 auto r = jrr[jss::node];
-
145
-
146 // we executed two create claim id txs
-
147 BEAST_EXPECT(r.isMember(sfXChainClaimID.jsonName));
-
148 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
-
149 }
-
150 }
-
151
-
152 void
-
153 testLedgerEntryClaimID()
-
154 {
-
155 testcase("ledger_entry: xchain_claim_id");
-
156 using namespace test::jtx;
-
157
-
158 Env mcEnv{*this, features};
-
159 Env scEnv(*this, envconfig(), features);
-
160
-
161 createBridgeObjects(mcEnv, scEnv);
-
162
-
163 scEnv(xchain_create_claim_id(scAlice, jvb, reward, mcAlice));
-
164 scEnv.close();
-
165 scEnv(xchain_create_claim_id(scBob, jvb, reward, mcBob));
-
166 scEnv.close();
-
167
-
168 std::string bridge_index;
+
135 {
+
136 // ask for a negative index
+
137 Json::Value jvParams;
+
138 jvParams[jss::ledger_index] = -1;
+
139 auto const jrr =
+
140 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
+
141 checkErrorValue(jrr, "invalidParams", "ledgerIndexMalformed");
+
142 }
+
143
+
144 {
+
145 // ask for a bad ledger index
+
146 Json::Value jvParams;
+
147 jvParams[jss::ledger_index] = 10u;
+
148 auto const jrr =
+
149 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
+
150 checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
+
151 }
+
152
+
153 {
+
154 // unrecognized string arg -- error
+
155 auto const jrr = env.rpc("ledger", "arbitrary_text")[jss::result];
+
156 checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
+
157 }
+
158
+
159 {
+
160 // Request queue for closed ledger
+
161 Json::Value jvParams;
+
162 jvParams[jss::ledger_index] = "validated";
+
163 jvParams[jss::queue] = true;
+
164 auto const jrr =
+
165 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
+
166 checkErrorValue(jrr, "invalidParams", "Invalid parameters.");
+
167 }
+
168
169 {
-
170 // request the xchain_claim_id via RPC
-
171 Json::Value jvParams;
-
172 jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
-
173 jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
-
174 1;
-
175 // std::cout << to_string(jvParams) << '\n';
-
176 Json::Value const jrr = scEnv.rpc(
-
177 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
178
-
179 BEAST_EXPECT(jrr.isMember(jss::node));
-
180 auto r = jrr[jss::node];
-
181 // std::cout << to_string(r) << '\n';
-
182
-
183 BEAST_EXPECT(r.isMember(jss::Account));
-
184 BEAST_EXPECT(r[jss::Account] == scAlice.human());
-
185 BEAST_EXPECT(
-
186 r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
-
187 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 1);
-
188 BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
-
189 }
+
170 // Request a ledger with a very large (double) sequence.
+
171 auto const ret =
+
172 env.rpc("json", "ledger", "{ \"ledger_index\" : 2e15 }");
+
173 BEAST_EXPECT(RPC::contains_error(ret));
+
174 BEAST_EXPECT(ret[jss::error_message] == "Invalid parameters.");
+
175 }
+
176
+
177 {
+
178 // Request a ledger with very large (integer) sequence.
+
179 auto const ret = env.rpc(
+
180 "json", "ledger", "{ \"ledger_index\" : 1000000000000000 }");
+
181 checkErrorValue(ret, "invalidParams", "Invalid parameters.");
+
182 }
+
183 }
+
184
+
185 void
+
186 testLedgerCurrent()
+
187 {
+
188 testcase("ledger_current Request");
+
189 using namespace test::jtx;
190
-
191 {
-
192 // request the xchain_claim_id via RPC
-
193 Json::Value jvParams;
-
194 jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
-
195 jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
-
196 2;
-
197 Json::Value const jrr = scEnv.rpc(
-
198 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
199
-
200 BEAST_EXPECT(jrr.isMember(jss::node));
-
201 auto r = jrr[jss::node];
-
202 // std::cout << to_string(r) << '\n';
-
203
-
204 BEAST_EXPECT(r.isMember(jss::Account));
-
205 BEAST_EXPECT(r[jss::Account] == scBob.human());
-
206 BEAST_EXPECT(
-
207 r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
-
208 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
-
209 BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
-
210 }
-
211 }
+
191 Env env{*this};
+
192
+
193 env.close();
+
194 BEAST_EXPECT(env.current()->info().seq == 4);
+
195
+
196 {
+
197 auto const jrr = env.rpc("ledger_current")[jss::result];
+
198 BEAST_EXPECT(
+
199 jrr[jss::ledger_current_index] == env.current()->info().seq);
+
200 }
+
201 }
+
202
+
203 void
+
204 testLedgerFull()
+
205 {
+
206 testcase("Ledger Request, Full Option");
+
207 using namespace test::jtx;
+
208
+
209 Env env{*this};
+
210
+
211 env.close();
212
-
213 void
-
214 testLedgerEntryCreateAccountClaimID()
-
215 {
-
216 testcase("ledger_entry: xchain_create_account_claim_id");
-
217 using namespace test::jtx;
-
218
-
219 Env mcEnv{*this, features};
-
220 Env scEnv(*this, envconfig(), features);
-
221
-
222 // note: signers.size() and quorum are both 5 in createBridgeObjects
-
223 createBridgeObjects(mcEnv, scEnv);
-
224
-
225 auto scCarol =
-
226 Account("scCarol"); // Don't fund it - it will be created with the
-
227 // xchain transaction
-
228 auto const amt = XRP(1000);
-
229 mcEnv(sidechain_xchain_account_create(
-
230 mcAlice, jvb, scCarol, amt, reward));
-
231 mcEnv.close();
+
213 Json::Value jvParams;
+
214 jvParams[jss::ledger_index] = 3u;
+
215 jvParams[jss::full] = true;
+
216 auto const jrr =
+
217 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
+
218 BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
+
219 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
+
220 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 3u);
+
221 }
+
222
+
223 void
+
224 testLedgerFullNonAdmin()
+
225 {
+
226 testcase("Ledger Request, Full Option Without Admin");
+
227 using namespace test::jtx;
+
228
+
229 Env env{*this, envconfig(no_admin)};
+
230
+
231 // env.close();
232
-
233 // send less than quorum of attestations (otherwise funds are
-
234 // immediately transferred and no "claim" object is created)
-
235 size_t constexpr num_attest = 3;
-
236 auto attestations = create_account_attestations(
-
237 scAttester,
-
238 jvb,
-
239 mcAlice,
-
240 amt,
-
241 reward,
-
242 payee,
-
243 /*wasLockingChainSend*/ true,
-
244 1,
-
245 scCarol,
-
246 signers,
-
247 UT_XCHAIN_DEFAULT_NUM_SIGNERS);
-
248 for (size_t i = 0; i < num_attest; ++i)
-
249 {
-
250 scEnv(attestations[i]);
-
251 }
-
252 scEnv.close();
-
253
-
254 {
-
255 // request the create account claim_id via RPC
-
256 Json::Value jvParams;
-
257 jvParams[jss::xchain_owned_create_account_claim_id] =
-
258 jvXRPBridgeRPC;
-
259 jvParams[jss::xchain_owned_create_account_claim_id]
-
260 [jss::xchain_owned_create_account_claim_id] = 1;
-
261 // std::cout << to_string(jvParams) << '\n';
-
262 Json::Value const jrr = scEnv.rpc(
-
263 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
264 // std::cout << to_string(jrr) << '\n';
-
265
-
266 BEAST_EXPECT(jrr.isMember(jss::node));
-
267 auto r = jrr[jss::node];
-
268
-
269 BEAST_EXPECT(r.isMember(jss::Account));
-
270 BEAST_EXPECT(r[jss::Account] == Account::master.human());
+
233 Json::Value jvParams;
+
234 jvParams[jss::ledger_index] = 1u;
+
235 jvParams[jss::full] = true;
+
236 auto const jrr =
+
237 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
+
238 checkErrorValue(
+
239 jrr, "noPermission", "You don't have permission for this command.");
+
240 }
+
241
+
242 void
+
243 testLedgerAccounts()
+
244 {
+
245 testcase("Ledger Request, Accounts Option");
+
246 using namespace test::jtx;
+
247
+
248 Env env{*this};
+
249
+
250 env.close();
+
251
+
252 Json::Value jvParams;
+
253 jvParams[jss::ledger_index] = 3u;
+
254 jvParams[jss::accounts] = true;
+
255 auto const jrr =
+
256 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
+
257 BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
+
258 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
+
259 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 3u);
+
260 }
+
261
+
266 void
+
267 testLookupLedger()
+
268 {
+
269 testcase("Lookup ledger");
+
270 using namespace test::jtx;
271
-
272 BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
-
273 BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 1);
-
274
-
275 BEAST_EXPECT(
-
276 r.isMember(sfXChainCreateAccountAttestations.jsonName));
-
277 auto attest = r[sfXChainCreateAccountAttestations.jsonName];
-
278 BEAST_EXPECT(attest.isArray());
-
279 BEAST_EXPECT(attest.size() == 3);
-
280 BEAST_EXPECT(attest[Json::Value::UInt(0)].isMember(
-
281 sfXChainCreateAccountProofSig.jsonName));
-
282 Json::Value a[num_attest];
-
283 for (size_t i = 0; i < num_attest; ++i)
-
284 {
-
285 a[i] = attest[Json::Value::UInt(0)]
-
286 [sfXChainCreateAccountProofSig.jsonName];
-
287 BEAST_EXPECT(
-
288 a[i].isMember(jss::Amount) &&
-
289 a[i][jss::Amount].asInt() == 1000 * drop_per_xrp);
-
290 BEAST_EXPECT(
-
291 a[i].isMember(jss::Destination) &&
-
292 a[i][jss::Destination] == scCarol.human());
-
293 BEAST_EXPECT(
-
294 a[i].isMember(sfAttestationSignerAccount.jsonName) &&
-
295 std::any_of(
-
296 signers.begin(), signers.end(), [&](signer const& s) {
-
297 return a[i][sfAttestationSignerAccount.jsonName] ==
-
298 s.account.human();
-
299 }));
-
300 BEAST_EXPECT(
-
301 a[i].isMember(sfAttestationRewardAccount.jsonName) &&
-
302 std::any_of(
-
303 payee.begin(),
-
304 payee.end(),
-
305 [&](Account const& account) {
-
306 return a[i][sfAttestationRewardAccount.jsonName] ==
-
307 account.human();
-
308 }));
-
309 BEAST_EXPECT(
-
310 a[i].isMember(sfWasLockingChainSend.jsonName) &&
-
311 a[i][sfWasLockingChainSend.jsonName] == 1);
-
312 BEAST_EXPECT(
-
313 a[i].isMember(sfSignatureReward.jsonName) &&
-
314 a[i][sfSignatureReward.jsonName].asInt() ==
-
315 1 * drop_per_xrp);
-
316 }
-
317 }
-
318
-
319 // complete attestations quorum - CreateAccountClaimID should not be
-
320 // present anymore
-
321 for (size_t i = num_attest; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS; ++i)
-
322 {
-
323 scEnv(attestations[i]);
-
324 }
-
325 scEnv.close();
-
326 {
-
327 // request the create account claim_id via RPC
-
328 Json::Value jvParams;
-
329 jvParams[jss::xchain_owned_create_account_claim_id] =
-
330 jvXRPBridgeRPC;
-
331 jvParams[jss::xchain_owned_create_account_claim_id]
-
332 [jss::xchain_owned_create_account_claim_id] = 1;
-
333 // std::cout << to_string(jvParams) << '\n';
-
334 Json::Value const jrr = scEnv.rpc(
-
335 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
336 checkErrorValue(jrr, "entryNotFound", "");
-
337 }
-
338 }
-
339
-
340public:
-
341 void
-
342 run() override
-
343 {
-
344 testLedgerEntryBridge();
-
345 testLedgerEntryClaimID();
-
346 testLedgerEntryCreateAccountClaimID();
-
347 }
-
348};
-
349
-
350class LedgerRPC_test : public beast::unit_test::suite
-
351{
-
352 void
-
353 checkErrorValue(
-
354 Json::Value const& jv,
-
355 std::string const& err,
-
356 std::string const& msg)
-
357 {
-
358 if (BEAST_EXPECT(jv.isMember(jss::status)))
-
359 BEAST_EXPECT(jv[jss::status] == "error");
-
360 if (BEAST_EXPECT(jv.isMember(jss::error)))
-
361 BEAST_EXPECT(jv[jss::error] == err);
-
362 if (msg.empty())
-
363 {
-
364 BEAST_EXPECT(
-
365 jv[jss::error_message] == Json::nullValue ||
-
366 jv[jss::error_message] == "");
-
367 }
-
368 else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
-
369 BEAST_EXPECT(jv[jss::error_message] == msg);
-
370 }
-
371
-
372 // Corrupt a valid address by replacing the 10th character with '!'.
-
373 // '!' is not part of the ripple alphabet.
-
374 std::string
-
375 makeBadAddress(std::string good)
-
376 {
-
377 std::string ret = std::move(good);
-
378 ret.replace(10, 1, 1, '!');
-
379 return ret;
-
380 }
-
381
-
382 void
-
383 testLedgerRequest()
-
384 {
-
385 testcase("Basic Request");
-
386 using namespace test::jtx;
-
387
-
388 Env env{*this};
-
389
-
390 env.close();
-
391 BEAST_EXPECT(env.current()->info().seq == 4);
-
392
-
393 {
-
394 Json::Value jvParams;
-
395 // can be either numeric or quoted numeric
-
396 jvParams[jss::ledger_index] = 1;
-
397 auto const jrr =
-
398 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
-
399 BEAST_EXPECT(jrr[jss::ledger][jss::closed] == true);
-
400 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "1");
-
401 }
-
402
-
403 {
-
404 Json::Value jvParams;
-
405 jvParams[jss::ledger_index] = "1";
-
406 auto const jrr =
-
407 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
-
408 BEAST_EXPECT(jrr[jss::ledger][jss::closed] == true);
-
409 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "1");
-
410 }
+
272 auto cfg = envconfig();
+
273 cfg->FEES.reference_fee = 10;
+
274 Env env{
+
275 *this, std::move(cfg), FeatureBitset{}}; // hashes requested below
+
276 // assume no amendments
+
277 env.fund(XRP(10000), "alice");
+
278 env.close();
+
279 env.fund(XRP(10000), "bob");
+
280 env.close();
+
281 env.fund(XRP(10000), "jim");
+
282 env.close();
+
283 env.fund(XRP(10000), "jill");
+
284
+
285 {
+
286 // access via the legacy ledger field, keyword index values
+
287 Json::Value jvParams;
+
288 jvParams[jss::ledger] = "closed";
+
289 auto jrr = env.rpc(
+
290 "json",
+
291 "ledger",
+
292 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
293 BEAST_EXPECT(jrr.isMember(jss::ledger));
+
294 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
+
295 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
+
296
+
297 jvParams[jss::ledger] = "validated";
+
298 jrr = env.rpc(
+
299 "json",
+
300 "ledger",
+
301 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
302 BEAST_EXPECT(jrr.isMember(jss::ledger));
+
303 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
+
304 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
+
305
+
306 jvParams[jss::ledger] = "current";
+
307 jrr = env.rpc(
+
308 "json",
+
309 "ledger",
+
310 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
311 BEAST_EXPECT(jrr.isMember(jss::ledger));
+
312 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "6");
+
313
+
314 // ask for a bad ledger keyword
+
315 jvParams[jss::ledger] = "invalid";
+
316 jrr = env.rpc(
+
317 "json",
+
318 "ledger",
+
319 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
320 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
+
321 BEAST_EXPECT(jrr[jss::error_message] == "ledgerIndexMalformed");
+
322
+
323 // numeric index
+
324 jvParams[jss::ledger] = 4;
+
325 jrr = env.rpc(
+
326 "json",
+
327 "ledger",
+
328 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
329 BEAST_EXPECT(jrr.isMember(jss::ledger));
+
330 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
+
331 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "4");
+
332
+
333 // numeric index - out of range
+
334 jvParams[jss::ledger] = 20;
+
335 jrr = env.rpc(
+
336 "json",
+
337 "ledger",
+
338 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
339 BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
+
340 BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
+
341 }
+
342
+
343 {
+
344 std::string const hash3{
+
345 "E86DE7F3D7A4D9CE17EF7C8BA08A8F4D"
+
346 "8F643B9552F0D895A31CDA78F541DE4E"};
+
347 // access via the ledger_hash field
+
348 Json::Value jvParams;
+
349 jvParams[jss::ledger_hash] = hash3;
+
350 auto jrr = env.rpc(
+
351 "json",
+
352 "ledger",
+
353 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
354 BEAST_EXPECT(jrr.isMember(jss::ledger));
+
355 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
+
356 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "3");
+
357
+
358 // extra leading hex chars in hash are not allowed
+
359 jvParams[jss::ledger_hash] = "DEADBEEF" + hash3;
+
360 jrr = env.rpc(
+
361 "json",
+
362 "ledger",
+
363 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
364 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
+
365 BEAST_EXPECT(jrr[jss::error_message] == "ledgerHashMalformed");
+
366
+
367 // request with non-string ledger_hash
+
368 jvParams[jss::ledger_hash] = 2;
+
369 jrr = env.rpc(
+
370 "json",
+
371 "ledger",
+
372 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
373 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
+
374 BEAST_EXPECT(jrr[jss::error_message] == "ledgerHashNotString");
+
375
+
376 // malformed (non hex) hash
+
377 jvParams[jss::ledger_hash] =
+
378 "2E81FC6EC0DD943197EGC7E3FBE9AE30"
+
379 "7F2775F2F7485BB37307984C3C0F2340";
+
380 jrr = env.rpc(
+
381 "json",
+
382 "ledger",
+
383 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
384 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
+
385 BEAST_EXPECT(jrr[jss::error_message] == "ledgerHashMalformed");
+
386
+
387 // properly formed, but just doesn't exist
+
388 jvParams[jss::ledger_hash] =
+
389 "8C3EEDB3124D92E49E75D81A8826A2E6"
+
390 "5A75FD71FC3FD6F36FEB803C5F1D812D";
+
391 jrr = env.rpc(
+
392 "json",
+
393 "ledger",
+
394 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
395 BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
+
396 BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
+
397 }
+
398
+
399 {
+
400 // access via the ledger_index field, keyword index values
+
401 Json::Value jvParams;
+
402 jvParams[jss::ledger_index] = "closed";
+
403 auto jrr = env.rpc(
+
404 "json",
+
405 "ledger",
+
406 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
407 BEAST_EXPECT(jrr.isMember(jss::ledger));
+
408 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
+
409 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
+
410 BEAST_EXPECT(jrr.isMember(jss::ledger_index));
411
-
412 {
-
413 // using current identifier
-
414 auto const jrr = env.rpc("ledger", "current")[jss::result];
-
415 BEAST_EXPECT(jrr[jss::ledger][jss::closed] == false);
-
416 BEAST_EXPECT(
-
417 jrr[jss::ledger][jss::ledger_index] ==
-
418 std::to_string(env.current()->info().seq));
-
419 BEAST_EXPECT(
-
420 jrr[jss::ledger_current_index] == env.current()->info().seq);
-
421 }
-
422 }
-
423
-
424 void
-
425 testBadInput()
-
426 {
-
427 testcase("Bad Input");
-
428 using namespace test::jtx;
-
429 Env env{*this};
-
430 Account const gw{"gateway"};
-
431 auto const USD = gw["USD"];
-
432 Account const bob{"bob"};
-
433
-
434 env.fund(XRP(10000), gw, bob);
-
435 env.close();
-
436 env.trust(USD(1000), bob);
-
437 env.close();
+
412 jvParams[jss::ledger_index] = "validated";
+
413 jrr = env.rpc(
+
414 "json",
+
415 "ledger",
+
416 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
417 BEAST_EXPECT(jrr.isMember(jss::ledger));
+
418 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
+
419 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
+
420
+
421 jvParams[jss::ledger_index] = "current";
+
422 jrr = env.rpc(
+
423 "json",
+
424 "ledger",
+
425 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
426 BEAST_EXPECT(jrr.isMember(jss::ledger));
+
427 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "6");
+
428 BEAST_EXPECT(jrr.isMember(jss::ledger_current_index));
+
429
+
430 // ask for a bad ledger keyword
+
431 jvParams[jss::ledger_index] = "invalid";
+
432 jrr = env.rpc(
+
433 "json",
+
434 "ledger",
+
435 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
436 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
+
437 BEAST_EXPECT(jrr[jss::error_message] == "ledgerIndexMalformed");
438
-
439 {
-
440 // ask for an arbitrary string - index
-
441 Json::Value jvParams;
-
442 jvParams[jss::ledger_index] = "potato";
-
443 auto const jrr =
-
444 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
-
445 checkErrorValue(jrr, "invalidParams", "ledgerIndexMalformed");
-
446 }
-
447
-
448 {
-
449 // ask for a negative index
-
450 Json::Value jvParams;
-
451 jvParams[jss::ledger_index] = -1;
-
452 auto const jrr =
-
453 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
-
454 checkErrorValue(jrr, "invalidParams", "ledgerIndexMalformed");
-
455 }
-
456
-
457 {
-
458 // ask for a bad ledger index
-
459 Json::Value jvParams;
-
460 jvParams[jss::ledger_index] = 10u;
-
461 auto const jrr =
-
462 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
-
463 checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
-
464 }
-
465
-
466 {
-
467 // unrecognized string arg -- error
-
468 auto const jrr = env.rpc("ledger", "arbitrary_text")[jss::result];
-
469 checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
-
470 }
+
439 // numeric index
+
440 for (auto i : {1, 2, 3, 4, 5, 6})
+
441 {
+
442 jvParams[jss::ledger_index] = i;
+
443 jrr = env.rpc(
+
444 "json",
+
445 "ledger",
+
446 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
447 BEAST_EXPECT(jrr.isMember(jss::ledger));
+
448 if (i < 6)
+
449 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
+
450 BEAST_EXPECT(
+
451 jrr[jss::ledger][jss::ledger_index] == std::to_string(i));
+
452 }
+
453
+
454 // numeric index - out of range
+
455 jvParams[jss::ledger_index] = 7;
+
456 jrr = env.rpc(
+
457 "json",
+
458 "ledger",
+
459 boost::lexical_cast<std::string>(jvParams))[jss::result];
+
460 BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
+
461 BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
+
462 }
+
463 }
+
464
+
465 void
+
466 testNoQueue()
+
467 {
+
468 testcase("Ledger with queueing disabled");
+
469 using namespace test::jtx;
+
470 Env env{*this};
471
-
472 {
-
473 // Request queue for closed ledger
-
474 Json::Value jvParams;
-
475 jvParams[jss::ledger_index] = "validated";
-
476 jvParams[jss::queue] = true;
-
477 auto const jrr =
-
478 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
-
479 checkErrorValue(jrr, "invalidParams", "Invalid parameters.");
-
480 }
-
481
-
482 {
-
483 // Request a ledger with a very large (double) sequence.
-
484 auto const ret =
-
485 env.rpc("json", "ledger", "{ \"ledger_index\" : 2e15 }");
-
486 BEAST_EXPECT(RPC::contains_error(ret));
-
487 BEAST_EXPECT(ret[jss::error_message] == "Invalid parameters.");
-
488 }
-
489
-
490 {
-
491 // Request a ledger with very large (integer) sequence.
-
492 auto const ret = env.rpc(
-
493 "json", "ledger", "{ \"ledger_index\" : 1000000000000000 }");
-
494 checkErrorValue(ret, "invalidParams", "Invalid parameters.");
-
495 }
-
496
-
497 {
-
498 // ask for an zero index
-
499 Json::Value jvParams;
-
500 jvParams[jss::ledger_index] = "validated";
-
501 jvParams[jss::index] =
-
502 "00000000000000000000000000000000000000000000000000000000000000"
-
503 "0000";
-
504 auto const jrr = env.rpc(
-
505 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
506 checkErrorValue(jrr, "malformedRequest", "");
-
507 }
-
508 }
-
509
-
510 void
-
511 testLedgerCurrent()
-
512 {
-
513 testcase("ledger_current Request");
-
514 using namespace test::jtx;
-
515
-
516 Env env{*this};
-
517
-
518 env.close();
-
519 BEAST_EXPECT(env.current()->info().seq == 4);
-
520
-
521 {
-
522 auto const jrr = env.rpc("ledger_current")[jss::result];
-
523 BEAST_EXPECT(
-
524 jrr[jss::ledger_current_index] == env.current()->info().seq);
-
525 }
-
526 }
-
527
-
528 void
-
529 testMissingLedgerEntryLedgerHash()
-
530 {
-
531 testcase("Missing ledger_entry ledger_hash");
-
532 using namespace test::jtx;
-
533 Env env{*this};
-
534 Account const alice{"alice"};
-
535 env.fund(XRP(10000), alice);
-
536 env.close();
-
537
-
538 Json::Value jvParams;
-
539 jvParams[jss::account_root] = alice.human();
-
540 jvParams[jss::ledger_hash] =
-
541 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
-
542 auto const jrr =
-
543 env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
-
544 checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
-
545 }
-
546
-
547 void
-
548 testLedgerFull()
-
549 {
-
550 testcase("Ledger Request, Full Option");
-
551 using namespace test::jtx;
+
472 Json::Value jv;
+
473 jv[jss::ledger_index] = "current";
+
474 jv[jss::queue] = true;
+
475 jv[jss::expand] = true;
+
476
+
477 auto jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
+
478 BEAST_EXPECT(!jrr.isMember(jss::queue_data));
+
479 }
+
480
+
481 void
+
482 testQueue()
+
483 {
+
484 testcase("Ledger with Queued Transactions");
+
485 using namespace test::jtx;
+
486 auto cfg = envconfig([](std::unique_ptr<Config> cfg) {
+
487 auto& section = cfg->section("transaction_queue");
+
488 section.set("minimum_txn_in_ledger_standalone", "3");
+
489 section.set("normal_consensus_increase_percent", "0");
+
490 return cfg;
+
491 });
+
492
+
493 cfg->FEES.reference_fee = 10;
+
494 Env env(*this, std::move(cfg));
+
495
+
496 Json::Value jv;
+
497 jv[jss::ledger_index] = "current";
+
498 jv[jss::queue] = true;
+
499 jv[jss::expand] = true;
+
500
+
501 Account const alice{"alice"};
+
502 Account const bob{"bob"};
+
503 Account const charlie{"charlie"};
+
504 Account const daria{"daria"};
+
505 env.fund(XRP(10000), alice);
+
506 env.fund(XRP(10000), bob);
+
507 env.close();
+
508 env.fund(XRP(10000), charlie);
+
509 env.fund(XRP(10000), daria);
+
510 env.close();
+
511
+
512 auto jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
+
513 BEAST_EXPECT(!jrr.isMember(jss::queue_data));
+
514
+
515 // Fill the open ledger
+
516 for (;;)
+
517 {
+
518 auto metrics = env.app().getTxQ().getMetrics(*env.current());
+
519 if (metrics.openLedgerFeeLevel > metrics.minProcessingFeeLevel)
+
520 break;
+
521 env(noop(alice));
+
522 }
+
523
+
524 BEAST_EXPECT(env.current()->info().seq == 5);
+
525 // Put some txs in the queue
+
526 // Alice
+
527 auto aliceSeq = env.seq(alice);
+
528 env(pay(alice, "george", XRP(1000)),
+
529 json(R"({"LastLedgerSequence":7})"),
+
530 ter(terQUEUED));
+
531 env(offer(alice, XRP(50000), alice["USD"](5000)),
+
532 seq(aliceSeq + 1),
+
533 ter(terQUEUED));
+
534 env(noop(alice), seq(aliceSeq + 2), ter(terQUEUED));
+
535 // Bob
+
536 auto batch = [&env](Account a) {
+
537 auto aSeq = env.seq(a);
+
538 // Enough fee to get in front of alice in the queue
+
539 for (int i = 0; i < 10; ++i)
+
540 {
+
541 env(noop(a), fee(1000 + i), seq(aSeq + i), ter(terQUEUED));
+
542 }
+
543 };
+
544 batch(bob);
+
545 // Charlie
+
546 batch(charlie);
+
547 // Daria
+
548 batch(daria);
+
549
+
550 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
+
551 BEAST_EXPECT(jrr[jss::queue_data].size() == 33);
552
-
553 Env env{*this};
-
554
-
555 env.close();
-
556
-
557 Json::Value jvParams;
-
558 jvParams[jss::ledger_index] = 3u;
-
559 jvParams[jss::full] = true;
-
560 auto const jrr =
-
561 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
-
562 BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
-
563 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
-
564 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 3u);
-
565 }
-
566
-
567 void
-
568 testLedgerFullNonAdmin()
-
569 {
-
570 testcase("Ledger Request, Full Option Without Admin");
-
571 using namespace test::jtx;
-
572
-
573 Env env{*this, envconfig(no_admin)};
-
574
-
575 // env.close();
-
576
-
577 Json::Value jvParams;
-
578 jvParams[jss::ledger_index] = 1u;
-
579 jvParams[jss::full] = true;
-
580 auto const jrr =
-
581 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
-
582 checkErrorValue(
-
583 jrr, "noPermission", "You don't have permission for this command.");
-
584 }
-
585
-
586 void
-
587 testLedgerAccounts()
-
588 {
-
589 testcase("Ledger Request, Accounts Option");
-
590 using namespace test::jtx;
-
591
-
592 Env env{*this};
-
593
-
594 env.close();
-
595
-
596 Json::Value jvParams;
-
597 jvParams[jss::ledger_index] = 3u;
-
598 jvParams[jss::accounts] = true;
-
599 auto const jrr =
-
600 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
-
601 BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
-
602 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
-
603 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 3u);
-
604 }
-
605
-
606 void
-
607 testLedgerEntryAccountRoot()
-
608 {
-
609 testcase("ledger_entry Request AccountRoot");
-
610 using namespace test::jtx;
-
611
-
612 auto cfg = envconfig();
-
613 cfg->FEES.reference_fee = 10;
-
614 Env env{*this, std::move(cfg)};
-
615 Account const alice{"alice"};
-
616 env.fund(XRP(10000), alice);
-
617 env.close();
-
618
-
619 std::string const ledgerHash{to_string(env.closed()->info().hash)};
-
620 {
-
621 // Exercise ledger_closed along the way.
-
622 Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
-
623 BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
-
624 BEAST_EXPECT(jrr[jss::ledger_index] == 3);
-
625 }
-
626
-
627 std::string accountRootIndex;
-
628 {
-
629 // Request alice's account root.
-
630 Json::Value jvParams;
-
631 jvParams[jss::account_root] = alice.human();
-
632 jvParams[jss::ledger_hash] = ledgerHash;
-
633 Json::Value const jrr = env.rpc(
-
634 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
635 BEAST_EXPECT(jrr.isMember(jss::node));
-
636 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
-
637 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
-
638 accountRootIndex = jrr[jss::index].asString();
-
639 }
-
640 {
-
641 constexpr char alicesAcctRootBinary[]{
-
642 "1100612200800000240000000425000000032D00000000559CE54C3B934E4"
-
643 "73A995B477E92EC229F99CED5B62BF4D2ACE4DC42719103AE2F6240000002"
-
644 "540BE4008114AE123A8556F3CF91154711376AFB0F894F832B3D"};
-
645
-
646 // Request alice's account root, but with binary == true;
-
647 Json::Value jvParams;
-
648 jvParams[jss::account_root] = alice.human();
-
649 jvParams[jss::binary] = 1;
-
650 jvParams[jss::ledger_hash] = ledgerHash;
-
651 Json::Value const jrr = env.rpc(
-
652 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
653 BEAST_EXPECT(jrr.isMember(jss::node_binary));
-
654 BEAST_EXPECT(jrr[jss::node_binary] == alicesAcctRootBinary);
-
655 }
-
656 {
-
657 // Request alice's account root using the index.
-
658 Json::Value jvParams;
-
659 jvParams[jss::index] = accountRootIndex;
-
660 Json::Value const jrr = env.rpc(
-
661 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
662 BEAST_EXPECT(!jrr.isMember(jss::node_binary));
-
663 BEAST_EXPECT(jrr.isMember(jss::node));
-
664 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
-
665 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
-
666 }
-
667 {
-
668 // Request alice's account root by index, but with binary == false.
-
669 Json::Value jvParams;
-
670 jvParams[jss::index] = accountRootIndex;
-
671 jvParams[jss::binary] = 0;
-
672 Json::Value const jrr = env.rpc(
-
673 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
674 BEAST_EXPECT(jrr.isMember(jss::node));
-
675 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
-
676 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
-
677 }
-
678 {
-
679 // Request using a corrupted AccountID.
-
680 Json::Value jvParams;
-
681 jvParams[jss::account_root] = makeBadAddress(alice.human());
-
682 jvParams[jss::ledger_hash] = ledgerHash;
-
683 Json::Value const jrr = env.rpc(
-
684 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
685 checkErrorValue(jrr, "malformedAddress", "");
-
686 }
+
553 // Close enough ledgers so that alice's first tx expires.
+
554 env.close();
+
555 env.close();
+
556 env.close();
+
557 BEAST_EXPECT(env.current()->info().seq == 8);
+
558
+
559 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
+
560 BEAST_EXPECT(jrr[jss::queue_data].size() == 11);
+
561
+
562 env.close();
+
563
+
564 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
+
565 const std::string txid0 = [&]() {
+
566 auto const& parentHash = env.current()->info().parentHash;
+
567 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
+
568 {
+
569 const std::string txid1 = [&]() {
+
570 auto const& txj = jrr[jss::queue_data][1u];
+
571 BEAST_EXPECT(txj[jss::account] == alice.human());
+
572 BEAST_EXPECT(txj[jss::fee_level] == "256");
+
573 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
+
574 BEAST_EXPECT(txj["retries_remaining"] == 10);
+
575 BEAST_EXPECT(txj.isMember(jss::tx));
+
576 auto const& tx = txj[jss::tx];
+
577 BEAST_EXPECT(tx[jss::Account] == alice.human());
+
578 BEAST_EXPECT(tx[jss::TransactionType] == jss::AccountSet);
+
579 return tx[jss::hash].asString();
+
580 }();
+
581
+
582 auto const& txj = jrr[jss::queue_data][0u];
+
583 BEAST_EXPECT(txj[jss::account] == alice.human());
+
584 BEAST_EXPECT(txj[jss::fee_level] == "256");
+
585 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
+
586 BEAST_EXPECT(txj["retries_remaining"] == 10);
+
587 BEAST_EXPECT(txj.isMember(jss::tx));
+
588 auto const& tx = txj[jss::tx];
+
589 BEAST_EXPECT(tx[jss::Account] == alice.human());
+
590 BEAST_EXPECT(tx[jss::TransactionType] == jss::OfferCreate);
+
591 const auto txid0 = tx[jss::hash].asString();
+
592 uint256 tx0, tx1;
+
593 BEAST_EXPECT(tx0.parseHex(txid0));
+
594 BEAST_EXPECT(tx1.parseHex(txid1));
+
595 BEAST_EXPECT((tx0 ^ parentHash) < (tx1 ^ parentHash));
+
596 return txid0;
+
597 }
+
598 return std::string{};
+
599 }();
+
600
+
601 env.close();
+
602
+
603 jv[jss::expand] = false;
+
604
+
605 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
+
606 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
+
607 {
+
608 auto const& parentHash = env.current()->info().parentHash;
+
609 auto const txid1 = [&]() {
+
610 auto const& txj = jrr[jss::queue_data][1u];
+
611 BEAST_EXPECT(txj[jss::account] == alice.human());
+
612 BEAST_EXPECT(txj[jss::fee_level] == "256");
+
613 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
+
614 BEAST_EXPECT(txj.isMember(jss::tx));
+
615 return txj[jss::tx].asString();
+
616 }();
+
617 auto const& txj = jrr[jss::queue_data][0u];
+
618 BEAST_EXPECT(txj[jss::account] == alice.human());
+
619 BEAST_EXPECT(txj[jss::fee_level] == "256");
+
620 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
+
621 BEAST_EXPECT(txj["retries_remaining"] == 9);
+
622 BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
+
623 BEAST_EXPECT(txj.isMember(jss::tx));
+
624 BEAST_EXPECT(txj[jss::tx] == txid0);
+
625 uint256 tx0, tx1;
+
626 BEAST_EXPECT(tx0.parseHex(txid0));
+
627 BEAST_EXPECT(tx1.parseHex(txid1));
+
628 BEAST_EXPECT((tx0 ^ parentHash) < (tx1 ^ parentHash));
+
629 }
+
630
+
631 env.close();
+
632
+
633 jv[jss::expand] = true;
+
634 jv[jss::binary] = true;
+
635
+
636 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
+
637 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
+
638 {
+
639 auto const& txj = jrr[jss::queue_data][1u];
+
640 BEAST_EXPECT(txj[jss::account] == alice.human());
+
641 BEAST_EXPECT(txj[jss::fee_level] == "256");
+
642 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
+
643 BEAST_EXPECT(txj["retries_remaining"] == 8);
+
644 BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
+
645 BEAST_EXPECT(txj.isMember(jss::tx));
+
646 BEAST_EXPECT(txj[jss::tx].isMember(jss::tx_blob));
+
647
+
648 auto const& txj2 = jrr[jss::queue_data][0u];
+
649 BEAST_EXPECT(txj2[jss::account] == alice.human());
+
650 BEAST_EXPECT(txj2[jss::fee_level] == "256");
+
651 BEAST_EXPECT(txj2["preflight_result"] == "tesSUCCESS");
+
652 BEAST_EXPECT(txj2["retries_remaining"] == 10);
+
653 BEAST_EXPECT(!txj2.isMember("last_result"));
+
654 BEAST_EXPECT(txj2.isMember(jss::tx));
+
655 BEAST_EXPECT(txj2[jss::tx].isMember(jss::tx_blob));
+
656 }
+
657
+
658 for (int i = 0; i != 9; ++i)
+
659 {
+
660 env.close();
+
661 }
+
662
+
663 jv[jss::expand] = false;
+
664 jv[jss::binary] = false;
+
665
+
666 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
+
667 const std::string txid2 = [&]() {
+
668 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 1))
+
669 {
+
670 auto const& txj = jrr[jss::queue_data][0u];
+
671 BEAST_EXPECT(txj[jss::account] == alice.human());
+
672 BEAST_EXPECT(txj[jss::fee_level] == "256");
+
673 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
+
674 BEAST_EXPECT(txj["retries_remaining"] == 1);
+
675 BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
+
676 BEAST_EXPECT(txj.isMember(jss::tx));
+
677 BEAST_EXPECT(txj[jss::tx] != txid0);
+
678 return txj[jss::tx].asString();
+
679 }
+
680 return std::string{};
+
681 }();
+
682
+
683 jv[jss::full] = true;
+
684
+
685 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
+
686 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 1))
687 {
-
688 // Request an account that is not in the ledger.
-
689 Json::Value jvParams;
-
690 jvParams[jss::account_root] = Account("bob").human();
-
691 jvParams[jss::ledger_hash] = ledgerHash;
-
692 Json::Value const jrr = env.rpc(
-
693 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
694 checkErrorValue(jrr, "entryNotFound", "");
-
695 }
-
696 }
-
697
-
698 void
-
699 testLedgerEntryCheck()
-
700 {
-
701 testcase("ledger_entry Request Check");
-
702 using namespace test::jtx;
-
703 Env env{*this};
-
704 Account const alice{"alice"};
-
705 env.fund(XRP(10000), alice);
-
706 env.close();
+
688 auto const& txj = jrr[jss::queue_data][0u];
+
689 BEAST_EXPECT(txj[jss::account] == alice.human());
+
690 BEAST_EXPECT(txj[jss::fee_level] == "256");
+
691 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
+
692 BEAST_EXPECT(txj["retries_remaining"] == 1);
+
693 BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
+
694 BEAST_EXPECT(txj.isMember(jss::tx));
+
695 auto const& tx = txj[jss::tx];
+
696 BEAST_EXPECT(tx[jss::Account] == alice.human());
+
697 BEAST_EXPECT(tx[jss::TransactionType] == jss::AccountSet);
+
698 BEAST_EXPECT(tx[jss::hash] == txid2);
+
699 }
+
700 }
+
701
+
702 void
+
703 testLedgerAccountsOption()
+
704 {
+
705 testcase("Ledger Request, Accounts Hashes");
+
706 using namespace test::jtx;
707
-
708 auto const checkId = keylet::check(env.master, env.seq(env.master));
+
708 Env env{*this};
709
-
710 env(check::create(env.master, alice, XRP(100)));
-
711 env.close();
-
712
-
713 std::string const ledgerHash{to_string(env.closed()->info().hash)};
-
714 {
-
715 // Request a check.
-
716 Json::Value jvParams;
-
717 jvParams[jss::check] = to_string(checkId.key);
-
718 jvParams[jss::ledger_hash] = ledgerHash;
-
719 Json::Value const jrr = env.rpc(
-
720 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
721 BEAST_EXPECT(
-
722 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
-
723 BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
-
724 }
-
725 {
-
726 // Request an index that is not a check. We'll use alice's
-
727 // account root index.
-
728 std::string accountRootIndex;
-
729 {
-
730 Json::Value jvParams;
-
731 jvParams[jss::account_root] = alice.human();
-
732 Json::Value const jrr = env.rpc(
-
733 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
734 accountRootIndex = jrr[jss::index].asString();
-
735 }
-
736 Json::Value jvParams;
-
737 jvParams[jss::check] = accountRootIndex;
-
738 jvParams[jss::ledger_hash] = ledgerHash;
-
739 Json::Value const jrr = env.rpc(
-
740 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
741 checkErrorValue(jrr, "unexpectedLedgerType", "");
-
742 }
-
743 }
-
744
+
710 env.close();
+
711
+
712 std::string index;
+
713 {
+
714 Json::Value jvParams;
+
715 jvParams[jss::ledger_index] = 3u;
+
716 jvParams[jss::accounts] = true;
+
717 jvParams[jss::expand] = true;
+
718 jvParams[jss::type] = "hashes";
+
719 auto const jrr =
+
720 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
+
721 BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
+
722 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
+
723 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 1u);
+
724 BEAST_EXPECT(
+
725 jrr[jss::ledger][jss::accountState][0u]["LedgerEntryType"] ==
+
726 jss::LedgerHashes);
+
727 index = jrr[jss::ledger][jss::accountState][0u]["index"].asString();
+
728 }
+
729 {
+
730 Json::Value jvParams;
+
731 jvParams[jss::ledger_index] = 3u;
+
732 jvParams[jss::accounts] = true;
+
733 jvParams[jss::expand] = false;
+
734 jvParams[jss::type] = "hashes";
+
735 auto const jrr =
+
736 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
+
737 BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
+
738 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
+
739 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 1u);
+
740 BEAST_EXPECT(jrr[jss::ledger][jss::accountState][0u] == index);
+
741 }
+
742 }
+
743
+
744public:
745 void
-
746 testLedgerEntryCredentials()
-
747 {
-
748 testcase("ledger_entry credentials");
-
749
-
750 using namespace test::jtx;
-
751
-
752 Env env(*this);
-
753 Account const issuer{"issuer"};
-
754 Account const alice{"alice"};
-
755 Account const bob{"bob"};
-
756 const char credType[] = "abcde";
-
757
-
758 env.fund(XRP(5000), issuer, alice, bob);
-
759 env.close();
+
746 run() override
+
747 {
+
748 testLedgerRequest();
+
749 testBadInput();
+
750 testLedgerCurrent();
+
751 testLedgerFull();
+
752 testLedgerFullNonAdmin();
+
753 testLedgerAccounts();
+
754 testLookupLedger();
+
755 testNoQueue();
+
756 testQueue();
+
757 testLedgerAccountsOption();
+
758 }
+
759};
760
-
761 // Setup credentials with DepositAuth object for Alice and Bob
-
762 env(credentials::create(alice, issuer, credType));
-
763 env.close();
-
764
-
765 {
-
766 // Succeed
-
767 auto jv = credentials::ledgerEntry(env, alice, issuer, credType);
-
768 BEAST_EXPECT(
-
769 jv.isObject() && jv.isMember(jss::result) &&
-
770 !jv[jss::result].isMember(jss::error) &&
-
771 jv[jss::result].isMember(jss::node) &&
-
772 jv[jss::result][jss::node].isMember(
-
773 sfLedgerEntryType.jsonName) &&
-
774 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
-
775 jss::Credential);
-
776
-
777 std::string const credIdx = jv[jss::result][jss::index].asString();
-
778
-
779 jv = credentials::ledgerEntry(env, credIdx);
-
780 BEAST_EXPECT(
-
781 jv.isObject() && jv.isMember(jss::result) &&
-
782 !jv[jss::result].isMember(jss::error) &&
-
783 jv[jss::result].isMember(jss::node) &&
-
784 jv[jss::result][jss::node].isMember(
-
785 sfLedgerEntryType.jsonName) &&
-
786 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
-
787 jss::Credential);
-
788 }
-
789
-
790 {
-
791 // Fail, index not a hash
-
792 auto const jv = credentials::ledgerEntry(env, "");
-
793 checkErrorValue(jv[jss::result], "malformedRequest", "");
-
794 }
-
795
-
796 {
-
797 // Fail, credential doesn't exist
-
798 auto const jv = credentials::ledgerEntry(
-
799 env,
-
800 "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
-
801 "E4");
-
802 checkErrorValue(jv[jss::result], "entryNotFound", "");
-
803 }
-
804
-
805 {
-
806 // Fail, invalid subject
-
807 Json::Value jv;
-
808 jv[jss::ledger_index] = jss::validated;
-
809 jv[jss::credential][jss::subject] = 42;
-
810 jv[jss::credential][jss::issuer] = issuer.human();
-
811 jv[jss::credential][jss::credential_type] =
-
812 strHex(std::string_view(credType));
-
813 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
-
814 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
815 }
-
816
-
817 {
-
818 // Fail, invalid issuer
-
819 Json::Value jv;
-
820 jv[jss::ledger_index] = jss::validated;
-
821 jv[jss::credential][jss::subject] = alice.human();
-
822 jv[jss::credential][jss::issuer] = 42;
-
823 jv[jss::credential][jss::credential_type] =
-
824 strHex(std::string_view(credType));
-
825 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
-
826 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
827 }
-
828
-
829 {
-
830 // Fail, invalid credentials type
-
831 Json::Value jv;
-
832 jv[jss::ledger_index] = jss::validated;
-
833 jv[jss::credential][jss::subject] = alice.human();
-
834 jv[jss::credential][jss::issuer] = issuer.human();
-
835 jv[jss::credential][jss::credential_type] = 42;
-
836 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
-
837 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
838 }
-
839
-
840 {
-
841 // Fail, empty subject
-
842 Json::Value jv;
-
843 jv[jss::ledger_index] = jss::validated;
-
844 jv[jss::credential][jss::subject] = "";
-
845 jv[jss::credential][jss::issuer] = issuer.human();
-
846 jv[jss::credential][jss::credential_type] =
-
847 strHex(std::string_view(credType));
-
848 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
-
849 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
850 }
-
851
-
852 {
-
853 // Fail, empty issuer
-
854 Json::Value jv;
-
855 jv[jss::ledger_index] = jss::validated;
-
856 jv[jss::credential][jss::subject] = alice.human();
-
857 jv[jss::credential][jss::issuer] = "";
-
858 jv[jss::credential][jss::credential_type] =
-
859 strHex(std::string_view(credType));
-
860 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
-
861 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
862 }
-
863
-
864 {
-
865 // Fail, empty credentials type
-
866 Json::Value jv;
-
867 jv[jss::ledger_index] = jss::validated;
-
868 jv[jss::credential][jss::subject] = alice.human();
-
869 jv[jss::credential][jss::issuer] = issuer.human();
-
870 jv[jss::credential][jss::credential_type] = "";
-
871 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
-
872 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
873 }
-
874
-
875 {
-
876 // Fail, no subject
-
877 Json::Value jv;
-
878 jv[jss::ledger_index] = jss::validated;
-
879 jv[jss::credential][jss::issuer] = issuer.human();
-
880 jv[jss::credential][jss::credential_type] =
-
881 strHex(std::string_view(credType));
-
882 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
-
883 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
884 }
-
885
-
886 {
-
887 // Fail, no issuer
-
888 Json::Value jv;
-
889 jv[jss::ledger_index] = jss::validated;
-
890 jv[jss::credential][jss::subject] = alice.human();
-
891 jv[jss::credential][jss::credential_type] =
-
892 strHex(std::string_view(credType));
-
893 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
-
894 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
895 }
-
896
-
897 {
-
898 // Fail, no credentials type
-
899 Json::Value jv;
-
900 jv[jss::ledger_index] = jss::validated;
-
901 jv[jss::credential][jss::subject] = alice.human();
-
902 jv[jss::credential][jss::issuer] = issuer.human();
-
903 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
-
904 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
905 }
-
906
-
907 {
-
908 // Fail, not AccountID subject
-
909 Json::Value jv;
-
910 jv[jss::ledger_index] = jss::validated;
-
911 jv[jss::credential][jss::subject] = "wehsdbvasbdfvj";
-
912 jv[jss::credential][jss::issuer] = issuer.human();
-
913 jv[jss::credential][jss::credential_type] =
-
914 strHex(std::string_view(credType));
-
915 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
-
916 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
917 }
-
918
-
919 {
-
920 // Fail, not AccountID issuer
-
921 Json::Value jv;
-
922 jv[jss::ledger_index] = jss::validated;
-
923 jv[jss::credential][jss::subject] = alice.human();
-
924 jv[jss::credential][jss::issuer] = "c4p93ugndfbsiu";
-
925 jv[jss::credential][jss::credential_type] =
-
926 strHex(std::string_view(credType));
-
927 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
-
928 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
929 }
-
930
-
931 {
-
932 // Fail, credentials type isn't hex encoded
-
933 Json::Value jv;
-
934 jv[jss::ledger_index] = jss::validated;
-
935 jv[jss::credential][jss::subject] = alice.human();
-
936 jv[jss::credential][jss::issuer] = issuer.human();
-
937 jv[jss::credential][jss::credential_type] = "12KK";
-
938 auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
-
939 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
940 }
-
941 }
-
942
-
943 void
-
944 testLedgerEntryDepositPreauth()
-
945 {
-
946 testcase("ledger_entry Deposit Preauth");
-
947
-
948 using namespace test::jtx;
-
949
-
950 Env env{*this};
-
951 Account const alice{"alice"};
-
952 Account const becky{"becky"};
-
953
-
954 env.fund(XRP(10000), alice, becky);
-
955 env.close();
-
956
-
957 env(deposit::auth(alice, becky));
-
958 env.close();
-
959
-
960 std::string const ledgerHash{to_string(env.closed()->info().hash)};
-
961 std::string depositPreauthIndex;
-
962 {
-
963 // Request a depositPreauth by owner and authorized.
-
964 Json::Value jvParams;
-
965 jvParams[jss::deposit_preauth][jss::owner] = alice.human();
-
966 jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
-
967 jvParams[jss::ledger_hash] = ledgerHash;
-
968 Json::Value const jrr = env.rpc(
-
969 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
970
-
971 BEAST_EXPECT(
-
972 jrr[jss::node][sfLedgerEntryType.jsonName] ==
-
973 jss::DepositPreauth);
-
974 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
-
975 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
-
976 depositPreauthIndex = jrr[jss::node][jss::index].asString();
-
977 }
-
978 {
-
979 // Request a depositPreauth by index.
-
980 Json::Value jvParams;
-
981 jvParams[jss::deposit_preauth] = depositPreauthIndex;
-
982 jvParams[jss::ledger_hash] = ledgerHash;
-
983 Json::Value const jrr = env.rpc(
-
984 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
985
-
986 BEAST_EXPECT(
-
987 jrr[jss::node][sfLedgerEntryType.jsonName] ==
-
988 jss::DepositPreauth);
-
989 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
-
990 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
-
991 }
-
992 {
-
993 // Malformed request: deposit_preauth neither object nor string.
-
994 Json::Value jvParams;
-
995 jvParams[jss::deposit_preauth] = -5;
-
996 jvParams[jss::ledger_hash] = ledgerHash;
-
997 Json::Value const jrr = env.rpc(
-
998 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
999 checkErrorValue(jrr, "malformedRequest", "");
-
1000 }
-
1001 {
-
1002 // Malformed request: deposit_preauth not hex string.
-
1003 Json::Value jvParams;
-
1004 jvParams[jss::deposit_preauth] = "0123456789ABCDEFG";
-
1005 jvParams[jss::ledger_hash] = ledgerHash;
-
1006 Json::Value const jrr = env.rpc(
-
1007 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1008 checkErrorValue(jrr, "malformedRequest", "");
-
1009 }
-
1010 {
-
1011 // Malformed request: missing [jss::deposit_preauth][jss::owner]
-
1012 Json::Value jvParams;
-
1013 jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
-
1014 jvParams[jss::ledger_hash] = ledgerHash;
-
1015 Json::Value const jrr = env.rpc(
-
1016 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1017 checkErrorValue(jrr, "malformedRequest", "");
-
1018 }
-
1019 {
-
1020 // Malformed request: [jss::deposit_preauth][jss::owner] not string.
-
1021 Json::Value jvParams;
-
1022 jvParams[jss::deposit_preauth][jss::owner] = 7;
-
1023 jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
-
1024 jvParams[jss::ledger_hash] = ledgerHash;
-
1025 Json::Value const jrr = env.rpc(
-
1026 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1027 checkErrorValue(jrr, "malformedRequest", "");
-
1028 }
-
1029 {
-
1030 // Malformed: missing [jss::deposit_preauth][jss::authorized]
-
1031 Json::Value jvParams;
-
1032 jvParams[jss::deposit_preauth][jss::owner] = alice.human();
-
1033 jvParams[jss::ledger_hash] = ledgerHash;
-
1034 Json::Value const jrr = env.rpc(
-
1035 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1036 checkErrorValue(jrr, "malformedRequest", "");
-
1037 }
-
1038 {
-
1039 // Malformed: [jss::deposit_preauth][jss::authorized] not string.
-
1040 Json::Value jvParams;
-
1041 jvParams[jss::deposit_preauth][jss::owner] = alice.human();
-
1042 jvParams[jss::deposit_preauth][jss::authorized] = 47;
-
1043 jvParams[jss::ledger_hash] = ledgerHash;
-
1044 Json::Value const jrr = env.rpc(
-
1045 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1046 checkErrorValue(jrr, "malformedRequest", "");
-
1047 }
-
1048 {
-
1049 // Malformed: [jss::deposit_preauth][jss::owner] is malformed.
-
1050 Json::Value jvParams;
-
1051 jvParams[jss::deposit_preauth][jss::owner] =
-
1052 "rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas";
-
1053
-
1054 jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
-
1055 jvParams[jss::ledger_hash] = ledgerHash;
-
1056 Json::Value const jrr = env.rpc(
-
1057 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1058 checkErrorValue(jrr, "malformedOwner", "");
-
1059 }
-
1060 {
-
1061 // Malformed: [jss::deposit_preauth][jss::authorized] is malformed.
-
1062 Json::Value jvParams;
-
1063 jvParams[jss::deposit_preauth][jss::owner] = alice.human();
-
1064 jvParams[jss::deposit_preauth][jss::authorized] =
-
1065 "rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas";
-
1066
-
1067 jvParams[jss::ledger_hash] = ledgerHash;
-
1068 Json::Value const jrr = env.rpc(
-
1069 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1070 checkErrorValue(jrr, "malformedAuthorized", "");
-
1071 }
-
1072 }
-
1073
-
1074 void
-
1075 testLedgerEntryDepositPreauthCred()
-
1076 {
-
1077 testcase("ledger_entry Deposit Preauth with credentials");
-
1078
-
1079 using namespace test::jtx;
-
1080
-
1081 Env env(*this);
-
1082 Account const issuer{"issuer"};
-
1083 Account const alice{"alice"};
-
1084 Account const bob{"bob"};
-
1085 const char credType[] = "abcde";
-
1086
-
1087 env.fund(XRP(5000), issuer, alice, bob);
-
1088 env.close();
-
1089
-
1090 {
-
1091 // Setup Bob with DepositAuth
-
1092 env(fset(bob, asfDepositAuth));
-
1093 env.close();
-
1094 env(deposit::authCredentials(bob, {{issuer, credType}}));
-
1095 env.close();
-
1096 }
-
1097
-
1098 {
-
1099 // Succeed
-
1100 Json::Value jvParams;
-
1101 jvParams[jss::ledger_index] = jss::validated;
-
1102 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1103
-
1104 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1105 Json::arrayValue;
-
1106 auto& arr(
-
1107 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1108
-
1109 Json::Value jo;
-
1110 jo[jss::issuer] = issuer.human();
-
1111 jo[jss::credential_type] = strHex(std::string_view(credType));
-
1112 arr.append(std::move(jo));
-
1113 auto const jrr =
-
1114 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1115
-
1116 BEAST_EXPECT(
-
1117 jrr.isObject() && jrr.isMember(jss::result) &&
-
1118 !jrr[jss::result].isMember(jss::error) &&
-
1119 jrr[jss::result].isMember(jss::node) &&
-
1120 jrr[jss::result][jss::node].isMember(
-
1121 sfLedgerEntryType.jsonName) &&
-
1122 jrr[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
-
1123 jss::DepositPreauth);
-
1124 }
-
1125
-
1126 {
-
1127 // Failed, invalid account
-
1128 Json::Value jvParams;
-
1129 jvParams[jss::ledger_index] = jss::validated;
-
1130 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1131
-
1132 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1133 Json::arrayValue;
-
1134 auto& arr(
-
1135 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1136
-
1137 Json::Value jo;
-
1138 jo[jss::issuer] = to_string(xrpAccount());
-
1139 jo[jss::credential_type] = strHex(std::string_view(credType));
-
1140 arr.append(std::move(jo));
-
1141 auto const jrr =
-
1142 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1143 checkErrorValue(
-
1144 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1145 }
-
1146
-
1147 {
-
1148 // Failed, duplicates in credentials
-
1149 Json::Value jvParams;
-
1150 jvParams[jss::ledger_index] = jss::validated;
-
1151 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1152
-
1153 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1154 Json::arrayValue;
-
1155 auto& arr(
-
1156 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1157
-
1158 Json::Value jo;
-
1159 jo[jss::issuer] = issuer.human();
-
1160 jo[jss::credential_type] = strHex(std::string_view(credType));
-
1161 arr.append(jo);
-
1162 arr.append(std::move(jo));
-
1163 auto const jrr =
-
1164 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1165 checkErrorValue(
-
1166 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1167 }
-
1168
-
1169 {
-
1170 // Failed, invalid credential_type
-
1171 Json::Value jvParams;
-
1172 jvParams[jss::ledger_index] = jss::validated;
-
1173 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1174
-
1175 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1176 Json::arrayValue;
-
1177 auto& arr(
-
1178 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1179
-
1180 Json::Value jo;
-
1181 jo[jss::issuer] = issuer.human();
-
1182 jo[jss::credential_type] = "";
-
1183 arr.append(std::move(jo));
-
1184
-
1185 auto const jrr =
-
1186 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1187 checkErrorValue(
-
1188 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1189 }
-
1190
-
1191 {
-
1192 // Failed, authorized and authorized_credentials both present
-
1193 Json::Value jvParams;
-
1194 jvParams[jss::ledger_index] = jss::validated;
-
1195 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1196 jvParams[jss::deposit_preauth][jss::authorized] = alice.human();
-
1197
-
1198 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1199 Json::arrayValue;
-
1200 auto& arr(
-
1201 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1202
-
1203 Json::Value jo;
-
1204 jo[jss::issuer] = issuer.human();
-
1205 jo[jss::credential_type] = strHex(std::string_view(credType));
-
1206 arr.append(std::move(jo));
-
1207
-
1208 auto const jrr =
-
1209 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1210 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
1211 }
-
1212
-
1213 {
-
1214 // Failed, authorized_credentials is not an array
-
1215 Json::Value jvParams;
-
1216 jvParams[jss::ledger_index] = jss::validated;
-
1217 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1218 jvParams[jss::deposit_preauth][jss::authorized_credentials] = 42;
-
1219
-
1220 auto const jrr =
-
1221 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1222 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
1223 }
-
1224
-
1225 {
-
1226 // Failed, authorized_credentials contains string data
-
1227 Json::Value jvParams;
-
1228 jvParams[jss::ledger_index] = jss::validated;
-
1229 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1230 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1231 Json::arrayValue;
-
1232 auto& arr(
-
1233 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1234 arr.append("foobar");
-
1235
-
1236 auto const jrr =
-
1237 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1238 checkErrorValue(
-
1239 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1240 }
-
1241
-
1242 {
-
1243 // Failed, authorized_credentials contains arrays
-
1244 Json::Value jvParams;
-
1245 jvParams[jss::ledger_index] = jss::validated;
-
1246 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1247 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1248 Json::arrayValue;
-
1249 auto& arr(
-
1250 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1251 Json::Value payload = Json::arrayValue;
-
1252 payload.append(42);
-
1253 arr.append(std::move(payload));
-
1254
-
1255 auto const jrr =
-
1256 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1257 checkErrorValue(
-
1258 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1259 }
-
1260
-
1261 {
-
1262 // Failed, authorized_credentials is empty array
-
1263 Json::Value jvParams;
-
1264 jvParams[jss::ledger_index] = jss::validated;
-
1265 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1266 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1267 Json::arrayValue;
-
1268
-
1269 auto const jrr =
-
1270 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1271 checkErrorValue(
-
1272 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1273 }
-
1274
-
1275 {
-
1276 // Failed, authorized_credentials is too long
-
1277
-
1278 static const std::string_view credTypes[] = {
-
1279 "cred1",
-
1280 "cred2",
-
1281 "cred3",
-
1282 "cred4",
-
1283 "cred5",
-
1284 "cred6",
-
1285 "cred7",
-
1286 "cred8",
-
1287 "cred9"};
-
1288 static_assert(
-
1289 sizeof(credTypes) / sizeof(credTypes[0]) >
-
1290 maxCredentialsArraySize);
-
1291
-
1292 Json::Value jvParams;
-
1293 jvParams[jss::ledger_index] = jss::validated;
-
1294 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1295 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1296 Json::arrayValue;
-
1297
-
1298 auto& arr(
-
1299 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1300
-
1301 for (unsigned i = 0; i < sizeof(credTypes) / sizeof(credTypes[0]);
-
1302 ++i)
-
1303 {
-
1304 Json::Value jo;
-
1305 jo[jss::issuer] = issuer.human();
-
1306 jo[jss::credential_type] =
-
1307 strHex(std::string_view(credTypes[i]));
-
1308 arr.append(std::move(jo));
-
1309 }
-
1310
-
1311 auto const jrr =
-
1312 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1313 checkErrorValue(
-
1314 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1315 }
-
1316
-
1317 {
-
1318 // Failed, issuer is not set
-
1319 Json::Value jvParams;
-
1320 jvParams[jss::ledger_index] = jss::validated;
-
1321 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1322
-
1323 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1324 Json::arrayValue;
-
1325 auto& arr(
-
1326 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1327
-
1328 Json::Value jo;
-
1329 jo[jss::credential_type] = strHex(std::string_view(credType));
-
1330 arr.append(std::move(jo));
-
1331
-
1332 auto const jrr =
-
1333 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1334 checkErrorValue(
-
1335 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1336 }
-
1337
-
1338 {
-
1339 // Failed, issuer isn't string
-
1340 Json::Value jvParams;
-
1341 jvParams[jss::ledger_index] = jss::validated;
-
1342 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1343
-
1344 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1345 Json::arrayValue;
-
1346 auto& arr(
-
1347 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1348
-
1349 Json::Value jo;
-
1350 jo[jss::issuer] = 42;
-
1351 jo[jss::credential_type] = strHex(std::string_view(credType));
-
1352 arr.append(std::move(jo));
-
1353
-
1354 auto const jrr =
-
1355 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1356 checkErrorValue(
-
1357 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1358 }
-
1359
-
1360 {
-
1361 // Failed, issuer is an array
-
1362 Json::Value jvParams;
-
1363 jvParams[jss::ledger_index] = jss::validated;
-
1364 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1365
-
1366 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1367 Json::arrayValue;
-
1368 auto& arr(
-
1369 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1370
-
1371 Json::Value jo;
-
1372 Json::Value payload = Json::arrayValue;
-
1373 payload.append(42);
-
1374 jo[jss::issuer] = std::move(payload);
-
1375 jo[jss::credential_type] = strHex(std::string_view(credType));
-
1376 arr.append(std::move(jo));
-
1377
-
1378 auto const jrr =
-
1379 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1380 checkErrorValue(
-
1381 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1382 }
-
1383
-
1384 {
-
1385 // Failed, issuer isn't valid encoded account
-
1386 Json::Value jvParams;
-
1387 jvParams[jss::ledger_index] = jss::validated;
-
1388 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1389
-
1390 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1391 Json::arrayValue;
-
1392 auto& arr(
-
1393 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1394
-
1395 Json::Value jo;
-
1396 jo[jss::issuer] = "invalid_account";
-
1397 jo[jss::credential_type] = strHex(std::string_view(credType));
-
1398 arr.append(std::move(jo));
-
1399
-
1400 auto const jrr =
-
1401 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1402 checkErrorValue(
-
1403 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1404 }
-
1405
-
1406 {
-
1407 // Failed, credential_type is not set
-
1408 Json::Value jvParams;
-
1409 jvParams[jss::ledger_index] = jss::validated;
-
1410 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1411
-
1412 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1413 Json::arrayValue;
-
1414 auto& arr(
-
1415 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1416
-
1417 Json::Value jo;
-
1418 jo[jss::issuer] = issuer.human();
-
1419 arr.append(std::move(jo));
-
1420
-
1421 auto const jrr =
-
1422 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1423 checkErrorValue(
-
1424 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1425 }
-
1426
-
1427 {
-
1428 // Failed, credential_type isn't string
-
1429 Json::Value jvParams;
-
1430 jvParams[jss::ledger_index] = jss::validated;
-
1431 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1432
-
1433 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1434 Json::arrayValue;
-
1435 auto& arr(
-
1436 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1437
-
1438 Json::Value jo;
-
1439 jo[jss::issuer] = issuer.human();
-
1440 jo[jss::credential_type] = 42;
-
1441 arr.append(std::move(jo));
-
1442
-
1443 auto const jrr =
-
1444 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1445 checkErrorValue(
-
1446 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1447 }
-
1448
-
1449 {
-
1450 // Failed, credential_type is an array
-
1451 Json::Value jvParams;
-
1452 jvParams[jss::ledger_index] = jss::validated;
-
1453 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1454
-
1455 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1456 Json::arrayValue;
-
1457 auto& arr(
-
1458 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1459
-
1460 Json::Value jo;
-
1461 jo[jss::issuer] = issuer.human();
-
1462 Json::Value payload = Json::arrayValue;
-
1463 payload.append(42);
-
1464 jo[jss::credential_type] = std::move(payload);
-
1465 arr.append(std::move(jo));
-
1466
-
1467 auto const jrr =
-
1468 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1469 checkErrorValue(
-
1470 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1471 }
-
1472
-
1473 {
-
1474 // Failed, credential_type isn't hex encoded
-
1475 Json::Value jvParams;
-
1476 jvParams[jss::ledger_index] = jss::validated;
-
1477 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
-
1478
-
1479 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
-
1480 Json::arrayValue;
-
1481 auto& arr(
-
1482 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
-
1483
-
1484 Json::Value jo;
-
1485 jo[jss::issuer] = issuer.human();
-
1486 jo[jss::credential_type] = "12KK";
-
1487 arr.append(std::move(jo));
-
1488
-
1489 auto const jrr =
-
1490 env.rpc("json", "ledger_entry", to_string(jvParams));
-
1491 checkErrorValue(
-
1492 jrr[jss::result], "malformedAuthorizedCredentials", "");
-
1493 }
-
1494 }
-
1495
-
1496 void
-
1497 testLedgerEntryDirectory()
-
1498 {
-
1499 testcase("ledger_entry Request Directory");
-
1500 using namespace test::jtx;
-
1501 Env env{*this};
-
1502 Account const alice{"alice"};
-
1503 Account const gw{"gateway"};
-
1504 auto const USD = gw["USD"];
-
1505 env.fund(XRP(10000), alice, gw);
-
1506 env.close();
-
1507
-
1508 env.trust(USD(1000), alice);
-
1509 env.close();
-
1510
-
1511 // Run up the number of directory entries so alice has two
-
1512 // directory nodes.
-
1513 for (int d = 1'000'032; d >= 1'000'000; --d)
-
1514 {
-
1515 env(offer(alice, USD(1), drops(d)));
-
1516 }
-
1517 env.close();
-
1518
-
1519 std::string const ledgerHash{to_string(env.closed()->info().hash)};
-
1520 {
-
1521 // Exercise ledger_closed along the way.
-
1522 Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
-
1523 BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
-
1524 BEAST_EXPECT(jrr[jss::ledger_index] == 5);
-
1525 }
-
1526
-
1527 std::string const dirRootIndex =
-
1528 "A33EC6BB85FB5674074C4A3A43373BB17645308F3EAE1933E3E35252162B217D";
-
1529 {
-
1530 // Locate directory by index.
-
1531 Json::Value jvParams;
-
1532 jvParams[jss::directory] = dirRootIndex;
-
1533 jvParams[jss::ledger_hash] = ledgerHash;
-
1534 Json::Value const jrr = env.rpc(
-
1535 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1536 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 32);
-
1537 }
-
1538 {
-
1539 // Locate directory by directory root.
-
1540 Json::Value jvParams;
-
1541 jvParams[jss::directory] = Json::objectValue;
-
1542 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
-
1543 Json::Value const jrr = env.rpc(
-
1544 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1545 BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
-
1546 }
-
1547 {
-
1548 // Locate directory by owner.
-
1549 Json::Value jvParams;
-
1550 jvParams[jss::directory] = Json::objectValue;
-
1551 jvParams[jss::directory][jss::owner] = alice.human();
-
1552 jvParams[jss::ledger_hash] = ledgerHash;
-
1553 Json::Value const jrr = env.rpc(
-
1554 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1555 BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
-
1556 }
-
1557 {
-
1558 // Locate directory by directory root and sub_index.
-
1559 Json::Value jvParams;
-
1560 jvParams[jss::directory] = Json::objectValue;
-
1561 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
-
1562 jvParams[jss::directory][jss::sub_index] = 1;
-
1563 Json::Value const jrr = env.rpc(
-
1564 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1565 BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
-
1566 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
-
1567 }
-
1568 {
-
1569 // Locate directory by owner and sub_index.
-
1570 Json::Value jvParams;
-
1571 jvParams[jss::directory] = Json::objectValue;
-
1572 jvParams[jss::directory][jss::owner] = alice.human();
-
1573 jvParams[jss::directory][jss::sub_index] = 1;
-
1574 jvParams[jss::ledger_hash] = ledgerHash;
-
1575 Json::Value const jrr = env.rpc(
-
1576 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1577 BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
-
1578 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
-
1579 }
-
1580 {
-
1581 // Null directory argument.
-
1582 Json::Value jvParams;
-
1583 jvParams[jss::directory] = Json::nullValue;
-
1584 jvParams[jss::ledger_hash] = ledgerHash;
-
1585 Json::Value const jrr = env.rpc(
-
1586 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1587 checkErrorValue(jrr, "malformedRequest", "");
-
1588 }
-
1589 {
-
1590 // Non-integer sub_index.
-
1591 Json::Value jvParams;
-
1592 jvParams[jss::directory] = Json::objectValue;
-
1593 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
-
1594 jvParams[jss::directory][jss::sub_index] = 1.5;
-
1595 jvParams[jss::ledger_hash] = ledgerHash;
-
1596 Json::Value const jrr = env.rpc(
-
1597 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1598 checkErrorValue(jrr, "malformedRequest", "");
-
1599 }
-
1600 {
-
1601 // Malformed owner entry.
-
1602 Json::Value jvParams;
-
1603 jvParams[jss::directory] = Json::objectValue;
-
1604
-
1605 std::string const badAddress = makeBadAddress(alice.human());
-
1606 jvParams[jss::directory][jss::owner] = badAddress;
-
1607 jvParams[jss::ledger_hash] = ledgerHash;
-
1608 Json::Value const jrr = env.rpc(
-
1609 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1610 checkErrorValue(jrr, "malformedAddress", "");
-
1611 }
-
1612 {
-
1613 // Malformed directory object. Specify both dir_root and owner.
-
1614 Json::Value jvParams;
-
1615 jvParams[jss::directory] = Json::objectValue;
-
1616 jvParams[jss::directory][jss::owner] = alice.human();
-
1617 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
-
1618 jvParams[jss::ledger_hash] = ledgerHash;
-
1619 Json::Value const jrr = env.rpc(
-
1620 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1621 checkErrorValue(jrr, "malformedRequest", "");
-
1622 }
-
1623 {
-
1624 // Incomplete directory object. Missing both dir_root and owner.
-
1625 Json::Value jvParams;
-
1626 jvParams[jss::directory] = Json::objectValue;
-
1627 jvParams[jss::directory][jss::sub_index] = 1;
-
1628 jvParams[jss::ledger_hash] = ledgerHash;
-
1629 Json::Value const jrr = env.rpc(
-
1630 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1631 checkErrorValue(jrr, "malformedRequest", "");
-
1632 }
-
1633 }
-
1634
-
1635 void
-
1636 testLedgerEntryEscrow()
-
1637 {
-
1638 testcase("ledger_entry Request Escrow");
-
1639 using namespace test::jtx;
-
1640 Env env{*this};
-
1641 Account const alice{"alice"};
-
1642 env.fund(XRP(10000), alice);
-
1643 env.close();
-
1644
-
1645 // Lambda to create an escrow.
-
1646 auto escrowCreate = [](test::jtx::Account const& account,
-
1647 test::jtx::Account const& to,
-
1648 STAmount const& amount,
-
1649 NetClock::time_point const& cancelAfter) {
-
1650 Json::Value jv;
-
1651 jv[jss::TransactionType] = jss::EscrowCreate;
-
1652 jv[jss::Flags] = tfUniversal;
-
1653 jv[jss::Account] = account.human();
-
1654 jv[jss::Destination] = to.human();
-
1655 jv[jss::Amount] = amount.getJson(JsonOptions::none);
-
1656 jv[sfFinishAfter.jsonName] =
-
1657 cancelAfter.time_since_epoch().count() + 2;
-
1658 return jv;
-
1659 };
-
1660
-
1661 using namespace std::chrono_literals;
-
1662 env(escrowCreate(alice, alice, XRP(333), env.now() + 2s));
-
1663 env.close();
-
1664
-
1665 std::string const ledgerHash{to_string(env.closed()->info().hash)};
-
1666 std::string escrowIndex;
-
1667 {
-
1668 // Request the escrow using owner and sequence.
-
1669 Json::Value jvParams;
-
1670 jvParams[jss::escrow] = Json::objectValue;
-
1671 jvParams[jss::escrow][jss::owner] = alice.human();
-
1672 jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1;
-
1673 Json::Value const jrr = env.rpc(
-
1674 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1675 BEAST_EXPECT(
-
1676 jrr[jss::node][jss::Amount] == XRP(333).value().getText());
-
1677 escrowIndex = jrr[jss::index].asString();
-
1678 }
-
1679 {
-
1680 // Request the escrow by index.
-
1681 Json::Value jvParams;
-
1682 jvParams[jss::escrow] = escrowIndex;
-
1683 jvParams[jss::ledger_hash] = ledgerHash;
-
1684 Json::Value const jrr = env.rpc(
-
1685 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1686 BEAST_EXPECT(
-
1687 jrr[jss::node][jss::Amount] == XRP(333).value().getText());
-
1688 }
-
1689 {
-
1690 // Malformed owner entry.
-
1691 Json::Value jvParams;
-
1692 jvParams[jss::escrow] = Json::objectValue;
-
1693
-
1694 std::string const badAddress = makeBadAddress(alice.human());
-
1695 jvParams[jss::escrow][jss::owner] = badAddress;
-
1696 jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1;
-
1697 jvParams[jss::ledger_hash] = ledgerHash;
-
1698 Json::Value const jrr = env.rpc(
-
1699 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1700 checkErrorValue(jrr, "malformedOwner", "");
-
1701 }
-
1702 {
-
1703 // Missing owner.
-
1704 Json::Value jvParams;
-
1705 jvParams[jss::escrow] = Json::objectValue;
-
1706 jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1;
-
1707 jvParams[jss::ledger_hash] = ledgerHash;
-
1708 Json::Value const jrr = env.rpc(
-
1709 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1710 checkErrorValue(jrr, "malformedRequest", "");
-
1711 }
-
1712 {
-
1713 // Missing sequence.
-
1714 Json::Value jvParams;
-
1715 jvParams[jss::escrow] = Json::objectValue;
-
1716 jvParams[jss::escrow][jss::owner] = alice.human();
-
1717 jvParams[jss::ledger_hash] = ledgerHash;
-
1718 Json::Value const jrr = env.rpc(
-
1719 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1720 checkErrorValue(jrr, "malformedRequest", "");
-
1721 }
-
1722 {
-
1723 // Non-integer sequence.
-
1724 Json::Value jvParams;
-
1725 jvParams[jss::escrow] = Json::objectValue;
-
1726 jvParams[jss::escrow][jss::owner] = alice.human();
-
1727 jvParams[jss::escrow][jss::seq] =
-
1728 std::to_string(env.seq(alice) - 1);
-
1729 jvParams[jss::ledger_hash] = ledgerHash;
-
1730 Json::Value const jrr = env.rpc(
-
1731 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1732 checkErrorValue(jrr, "malformedRequest", "");
-
1733 }
-
1734 }
-
1735
-
1736 void
-
1737 testLedgerEntryOffer()
-
1738 {
-
1739 testcase("ledger_entry Request Offer");
-
1740 using namespace test::jtx;
-
1741 Env env{*this};
-
1742 Account const alice{"alice"};
-
1743 Account const gw{"gateway"};
-
1744 auto const USD = gw["USD"];
-
1745 env.fund(XRP(10000), alice, gw);
-
1746 env.close();
-
1747
-
1748 env(offer(alice, USD(321), XRP(322)));
-
1749 env.close();
-
1750
-
1751 std::string const ledgerHash{to_string(env.closed()->info().hash)};
-
1752 std::string offerIndex;
-
1753 {
-
1754 // Request the offer using owner and sequence.
-
1755 Json::Value jvParams;
-
1756 jvParams[jss::offer] = Json::objectValue;
-
1757 jvParams[jss::offer][jss::account] = alice.human();
-
1758 jvParams[jss::offer][jss::seq] = env.seq(alice) - 1;
-
1759 jvParams[jss::ledger_hash] = ledgerHash;
-
1760 Json::Value const jrr = env.rpc(
-
1761 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1762 BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
-
1763 offerIndex = jrr[jss::index].asString();
-
1764 }
-
1765 {
-
1766 // Request the offer using its index.
-
1767 Json::Value jvParams;
-
1768 jvParams[jss::offer] = offerIndex;
-
1769 Json::Value const jrr = env.rpc(
-
1770 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1771 BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
-
1772 }
-
1773 {
-
1774 // Malformed account entry.
-
1775 Json::Value jvParams;
-
1776 jvParams[jss::offer] = Json::objectValue;
-
1777
-
1778 std::string const badAddress = makeBadAddress(alice.human());
-
1779 jvParams[jss::offer][jss::account] = badAddress;
-
1780 jvParams[jss::offer][jss::seq] = env.seq(alice) - 1;
-
1781 jvParams[jss::ledger_hash] = ledgerHash;
-
1782 Json::Value const jrr = env.rpc(
-
1783 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1784 checkErrorValue(jrr, "malformedAddress", "");
-
1785 }
-
1786 {
-
1787 // Malformed offer object. Missing account member.
-
1788 Json::Value jvParams;
-
1789 jvParams[jss::offer] = Json::objectValue;
-
1790 jvParams[jss::offer][jss::seq] = env.seq(alice) - 1;
-
1791 jvParams[jss::ledger_hash] = ledgerHash;
-
1792 Json::Value const jrr = env.rpc(
-
1793 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1794 checkErrorValue(jrr, "malformedRequest", "");
-
1795 }
-
1796 {
-
1797 // Malformed offer object. Missing seq member.
-
1798 Json::Value jvParams;
-
1799 jvParams[jss::offer] = Json::objectValue;
-
1800 jvParams[jss::offer][jss::account] = alice.human();
-
1801 jvParams[jss::ledger_hash] = ledgerHash;
-
1802 Json::Value const jrr = env.rpc(
-
1803 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1804 checkErrorValue(jrr, "malformedRequest", "");
-
1805 }
-
1806 {
-
1807 // Malformed offer object. Non-integral seq member.
-
1808 Json::Value jvParams;
-
1809 jvParams[jss::offer] = Json::objectValue;
-
1810 jvParams[jss::offer][jss::account] = alice.human();
-
1811 jvParams[jss::offer][jss::seq] = std::to_string(env.seq(alice) - 1);
-
1812 jvParams[jss::ledger_hash] = ledgerHash;
-
1813 Json::Value const jrr = env.rpc(
-
1814 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1815 checkErrorValue(jrr, "malformedRequest", "");
-
1816 }
-
1817 }
-
1818
-
1819 void
-
1820 testLedgerEntryPayChan()
-
1821 {
-
1822 testcase("ledger_entry Request Pay Chan");
-
1823 using namespace test::jtx;
-
1824 using namespace std::literals::chrono_literals;
-
1825 Env env{*this};
-
1826 Account const alice{"alice"};
-
1827
-
1828 env.fund(XRP(10000), alice);
-
1829 env.close();
-
1830
-
1831 // Lambda to create a PayChan.
-
1832 auto payChanCreate = [](test::jtx::Account const& account,
-
1833 test::jtx::Account const& to,
-
1834 STAmount const& amount,
-
1835 NetClock::duration const& settleDelay,
-
1836 PublicKey const& pk) {
-
1837 Json::Value jv;
-
1838 jv[jss::TransactionType] = jss::PaymentChannelCreate;
-
1839 jv[jss::Account] = account.human();
-
1840 jv[jss::Destination] = to.human();
-
1841 jv[jss::Amount] = amount.getJson(JsonOptions::none);
-
1842 jv[sfSettleDelay.jsonName] = settleDelay.count();
-
1843 jv[sfPublicKey.jsonName] = strHex(pk.slice());
-
1844 return jv;
-
1845 };
-
1846
-
1847 env(payChanCreate(alice, env.master, XRP(57), 18s, alice.pk()));
-
1848 env.close();
-
1849
-
1850 std::string const ledgerHash{to_string(env.closed()->info().hash)};
-
1851
-
1852 uint256 const payChanIndex{
-
1853 keylet::payChan(alice, env.master, env.seq(alice) - 1).key};
-
1854 {
-
1855 // Request the payment channel using its index.
-
1856 Json::Value jvParams;
-
1857 jvParams[jss::payment_channel] = to_string(payChanIndex);
-
1858 jvParams[jss::ledger_hash] = ledgerHash;
-
1859 Json::Value const jrr = env.rpc(
-
1860 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1861 BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "57000000");
-
1862 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "0");
-
1863 BEAST_EXPECT(jrr[jss::node][sfSettleDelay.jsonName] == 18);
-
1864 }
-
1865 {
-
1866 // Request an index that is not a payment channel.
-
1867 Json::Value jvParams;
-
1868 jvParams[jss::payment_channel] = ledgerHash;
-
1869 jvParams[jss::ledger_hash] = ledgerHash;
-
1870 Json::Value const jrr = env.rpc(
-
1871 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1872 checkErrorValue(jrr, "entryNotFound", "");
-
1873 }
-
1874 }
-
1875
-
1876 void
-
1877 testLedgerEntryRippleState()
-
1878 {
-
1879 testcase("ledger_entry Request RippleState");
-
1880 using namespace test::jtx;
-
1881 Env env{*this};
-
1882 Account const alice{"alice"};
-
1883 Account const gw{"gateway"};
-
1884 auto const USD = gw["USD"];
-
1885 env.fund(XRP(10000), alice, gw);
-
1886 env.close();
-
1887
-
1888 env.trust(USD(999), alice);
-
1889 env.close();
-
1890
-
1891 env(pay(gw, alice, USD(97)));
-
1892 env.close();
-
1893
-
1894 // check both aliases
-
1895 for (auto const& fieldName : {jss::ripple_state, jss::state})
-
1896 {
-
1897 std::string const ledgerHash{to_string(env.closed()->info().hash)};
-
1898 {
-
1899 // Request the trust line using the accounts and currency.
-
1900 Json::Value jvParams;
-
1901 jvParams[fieldName] = Json::objectValue;
-
1902 jvParams[fieldName][jss::accounts] = Json::arrayValue;
-
1903 jvParams[fieldName][jss::accounts][0u] = alice.human();
-
1904 jvParams[fieldName][jss::accounts][1u] = gw.human();
-
1905 jvParams[fieldName][jss::currency] = "USD";
-
1906 jvParams[jss::ledger_hash] = ledgerHash;
-
1907 Json::Value const jrr = env.rpc(
-
1908 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1909 BEAST_EXPECT(
-
1910 jrr[jss::node][sfBalance.jsonName][jss::value] == "-97");
-
1911 BEAST_EXPECT(
-
1912 jrr[jss::node][sfHighLimit.jsonName][jss::value] == "999");
-
1913 }
-
1914 {
-
1915 // ripple_state is not an object.
-
1916 Json::Value jvParams;
-
1917 jvParams[fieldName] = "ripple_state";
-
1918 jvParams[jss::ledger_hash] = ledgerHash;
-
1919 Json::Value const jrr = env.rpc(
-
1920 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1921 checkErrorValue(jrr, "malformedRequest", "");
-
1922 }
-
1923 {
-
1924 // ripple_state.currency is missing.
-
1925 Json::Value jvParams;
-
1926 jvParams[fieldName] = Json::objectValue;
-
1927 jvParams[fieldName][jss::accounts] = Json::arrayValue;
-
1928 jvParams[fieldName][jss::accounts][0u] = alice.human();
-
1929 jvParams[fieldName][jss::accounts][1u] = gw.human();
-
1930 jvParams[jss::ledger_hash] = ledgerHash;
-
1931 Json::Value const jrr = env.rpc(
-
1932 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1933 checkErrorValue(jrr, "malformedRequest", "");
-
1934 }
-
1935 {
-
1936 // ripple_state accounts is not an array.
-
1937 Json::Value jvParams;
-
1938 jvParams[fieldName] = Json::objectValue;
-
1939 jvParams[fieldName][jss::accounts] = 2;
-
1940 jvParams[fieldName][jss::currency] = "USD";
-
1941 jvParams[jss::ledger_hash] = ledgerHash;
-
1942 Json::Value const jrr = env.rpc(
-
1943 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1944 checkErrorValue(jrr, "malformedRequest", "");
-
1945 }
-
1946 {
-
1947 // ripple_state one of the accounts is missing.
-
1948 Json::Value jvParams;
-
1949 jvParams[fieldName] = Json::objectValue;
-
1950 jvParams[fieldName][jss::accounts] = Json::arrayValue;
-
1951 jvParams[fieldName][jss::accounts][0u] = alice.human();
-
1952 jvParams[fieldName][jss::currency] = "USD";
-
1953 jvParams[jss::ledger_hash] = ledgerHash;
-
1954 Json::Value const jrr = env.rpc(
-
1955 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1956 checkErrorValue(jrr, "malformedRequest", "");
-
1957 }
-
1958 {
-
1959 // ripple_state more than 2 accounts.
-
1960 Json::Value jvParams;
-
1961 jvParams[fieldName] = Json::objectValue;
-
1962 jvParams[fieldName][jss::accounts] = Json::arrayValue;
-
1963 jvParams[fieldName][jss::accounts][0u] = alice.human();
-
1964 jvParams[fieldName][jss::accounts][1u] = gw.human();
-
1965 jvParams[fieldName][jss::accounts][2u] = alice.human();
-
1966 jvParams[fieldName][jss::currency] = "USD";
-
1967 jvParams[jss::ledger_hash] = ledgerHash;
-
1968 Json::Value const jrr = env.rpc(
-
1969 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1970 checkErrorValue(jrr, "malformedRequest", "");
-
1971 }
-
1972 {
-
1973 // ripple_state account[0] is not a string.
-
1974 Json::Value jvParams;
-
1975 jvParams[fieldName] = Json::objectValue;
-
1976 jvParams[fieldName][jss::accounts] = Json::arrayValue;
-
1977 jvParams[fieldName][jss::accounts][0u] = 44;
-
1978 jvParams[fieldName][jss::accounts][1u] = gw.human();
-
1979 jvParams[fieldName][jss::currency] = "USD";
-
1980 jvParams[jss::ledger_hash] = ledgerHash;
-
1981 Json::Value const jrr = env.rpc(
-
1982 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1983 checkErrorValue(jrr, "malformedRequest", "");
-
1984 }
-
1985 {
-
1986 // ripple_state account[1] is not a string.
-
1987 Json::Value jvParams;
-
1988 jvParams[fieldName] = Json::objectValue;
-
1989 jvParams[fieldName][jss::accounts] = Json::arrayValue;
-
1990 jvParams[fieldName][jss::accounts][0u] = alice.human();
-
1991 jvParams[fieldName][jss::accounts][1u] = 21;
-
1992 jvParams[fieldName][jss::currency] = "USD";
-
1993 jvParams[jss::ledger_hash] = ledgerHash;
-
1994 Json::Value const jrr = env.rpc(
-
1995 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
1996 checkErrorValue(jrr, "malformedRequest", "");
-
1997 }
-
1998 {
-
1999 // ripple_state account[0] == account[1].
-
2000 Json::Value jvParams;
-
2001 jvParams[fieldName] = Json::objectValue;
-
2002 jvParams[fieldName][jss::accounts] = Json::arrayValue;
-
2003 jvParams[fieldName][jss::accounts][0u] = alice.human();
-
2004 jvParams[fieldName][jss::accounts][1u] = alice.human();
-
2005 jvParams[fieldName][jss::currency] = "USD";
-
2006 jvParams[jss::ledger_hash] = ledgerHash;
-
2007 Json::Value const jrr = env.rpc(
-
2008 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2009 checkErrorValue(jrr, "malformedRequest", "");
-
2010 }
-
2011 {
-
2012 // ripple_state malformed account[0].
-
2013 Json::Value jvParams;
-
2014 jvParams[fieldName] = Json::objectValue;
-
2015 jvParams[fieldName][jss::accounts] = Json::arrayValue;
-
2016 jvParams[fieldName][jss::accounts][0u] =
-
2017 makeBadAddress(alice.human());
-
2018 jvParams[fieldName][jss::accounts][1u] = gw.human();
-
2019 jvParams[fieldName][jss::currency] = "USD";
-
2020 jvParams[jss::ledger_hash] = ledgerHash;
-
2021 Json::Value const jrr = env.rpc(
-
2022 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2023 checkErrorValue(jrr, "malformedAddress", "");
-
2024 }
-
2025 {
-
2026 // ripple_state malformed account[1].
-
2027 Json::Value jvParams;
-
2028 jvParams[fieldName] = Json::objectValue;
-
2029 jvParams[fieldName][jss::accounts] = Json::arrayValue;
-
2030 jvParams[fieldName][jss::accounts][0u] = alice.human();
-
2031 jvParams[fieldName][jss::accounts][1u] =
-
2032 makeBadAddress(gw.human());
-
2033 jvParams[fieldName][jss::currency] = "USD";
-
2034 jvParams[jss::ledger_hash] = ledgerHash;
-
2035 Json::Value const jrr = env.rpc(
-
2036 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2037 checkErrorValue(jrr, "malformedAddress", "");
-
2038 }
-
2039 {
-
2040 // ripple_state malformed currency.
-
2041 Json::Value jvParams;
-
2042 jvParams[fieldName] = Json::objectValue;
-
2043 jvParams[fieldName][jss::accounts] = Json::arrayValue;
-
2044 jvParams[fieldName][jss::accounts][0u] = alice.human();
-
2045 jvParams[fieldName][jss::accounts][1u] = gw.human();
-
2046 jvParams[fieldName][jss::currency] = "USDollars";
-
2047 jvParams[jss::ledger_hash] = ledgerHash;
-
2048 Json::Value const jrr = env.rpc(
-
2049 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2050 checkErrorValue(jrr, "malformedCurrency", "");
-
2051 }
-
2052 }
-
2053 }
-
2054
-
2055 void
-
2056 testLedgerEntryTicket()
-
2057 {
-
2058 testcase("ledger_entry Request Ticket");
-
2059 using namespace test::jtx;
-
2060 Env env{*this};
-
2061 env.close();
-
2062
-
2063 // Create two tickets.
-
2064 std::uint32_t const tkt1{env.seq(env.master) + 1};
-
2065 env(ticket::create(env.master, 2));
-
2066 env.close();
-
2067
-
2068 std::string const ledgerHash{to_string(env.closed()->info().hash)};
-
2069 // Request four tickets: one before the first one we created, the
-
2070 // two created tickets, and the ticket that would come after the
-
2071 // last created ticket.
-
2072 {
-
2073 // Not a valid ticket requested by index.
-
2074 Json::Value jvParams;
-
2075 jvParams[jss::ticket] =
-
2076 to_string(getTicketIndex(env.master, tkt1 - 1));
-
2077 jvParams[jss::ledger_hash] = ledgerHash;
-
2078 Json::Value const jrr = env.rpc(
-
2079 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2080 checkErrorValue(jrr, "entryNotFound", "");
-
2081 }
-
2082 {
-
2083 // First real ticket requested by index.
-
2084 Json::Value jvParams;
-
2085 jvParams[jss::ticket] = to_string(getTicketIndex(env.master, tkt1));
-
2086 jvParams[jss::ledger_hash] = ledgerHash;
-
2087 Json::Value const jrr = env.rpc(
-
2088 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2089 BEAST_EXPECT(
-
2090 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Ticket);
-
2091 BEAST_EXPECT(jrr[jss::node][sfTicketSequence.jsonName] == tkt1);
-
2092 }
-
2093 {
-
2094 // Second real ticket requested by account and sequence.
-
2095 Json::Value jvParams;
-
2096 jvParams[jss::ticket] = Json::objectValue;
-
2097 jvParams[jss::ticket][jss::account] = env.master.human();
-
2098 jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 1;
-
2099 jvParams[jss::ledger_hash] = ledgerHash;
-
2100 Json::Value const jrr = env.rpc(
-
2101 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2102 BEAST_EXPECT(
-
2103 jrr[jss::node][jss::index] ==
-
2104 to_string(getTicketIndex(env.master, tkt1 + 1)));
-
2105 }
-
2106 {
-
2107 // Not a valid ticket requested by account and sequence.
-
2108 Json::Value jvParams;
-
2109 jvParams[jss::ticket] = Json::objectValue;
-
2110 jvParams[jss::ticket][jss::account] = env.master.human();
-
2111 jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 2;
-
2112 jvParams[jss::ledger_hash] = ledgerHash;
-
2113 Json::Value const jrr = env.rpc(
-
2114 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2115 checkErrorValue(jrr, "entryNotFound", "");
-
2116 }
-
2117 {
-
2118 // Request a ticket using an account root entry.
-
2119 Json::Value jvParams;
-
2120 jvParams[jss::ticket] = to_string(keylet::account(env.master).key);
-
2121 jvParams[jss::ledger_hash] = ledgerHash;
-
2122 Json::Value const jrr = env.rpc(
-
2123 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2124 checkErrorValue(jrr, "unexpectedLedgerType", "");
-
2125 }
-
2126 {
-
2127 // Malformed account entry.
-
2128 Json::Value jvParams;
-
2129 jvParams[jss::ticket] = Json::objectValue;
-
2130
-
2131 std::string const badAddress = makeBadAddress(env.master.human());
-
2132 jvParams[jss::ticket][jss::account] = badAddress;
-
2133 jvParams[jss::ticket][jss::ticket_seq] = env.seq(env.master) - 1;
-
2134 jvParams[jss::ledger_hash] = ledgerHash;
-
2135 Json::Value const jrr = env.rpc(
-
2136 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2137 checkErrorValue(jrr, "malformedAddress", "");
-
2138 }
-
2139 {
-
2140 // Malformed ticket object. Missing account member.
-
2141 Json::Value jvParams;
-
2142 jvParams[jss::ticket] = Json::objectValue;
-
2143 jvParams[jss::ticket][jss::ticket_seq] = env.seq(env.master) - 1;
-
2144 jvParams[jss::ledger_hash] = ledgerHash;
-
2145 Json::Value const jrr = env.rpc(
-
2146 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2147 checkErrorValue(jrr, "malformedRequest", "");
-
2148 }
-
2149 {
-
2150 // Malformed ticket object. Missing seq member.
-
2151 Json::Value jvParams;
-
2152 jvParams[jss::ticket] = Json::objectValue;
-
2153 jvParams[jss::ticket][jss::account] = env.master.human();
-
2154 jvParams[jss::ledger_hash] = ledgerHash;
-
2155 Json::Value const jrr = env.rpc(
-
2156 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2157 checkErrorValue(jrr, "malformedRequest", "");
-
2158 }
-
2159 {
-
2160 // Malformed ticket object. Non-integral seq member.
-
2161 Json::Value jvParams;
-
2162 jvParams[jss::ticket] = Json::objectValue;
-
2163 jvParams[jss::ticket][jss::account] = env.master.human();
-
2164 jvParams[jss::ticket][jss::ticket_seq] =
-
2165 std::to_string(env.seq(env.master) - 1);
-
2166 jvParams[jss::ledger_hash] = ledgerHash;
-
2167 Json::Value const jrr = env.rpc(
-
2168 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2169 checkErrorValue(jrr, "malformedRequest", "");
-
2170 }
-
2171 }
-
2172
-
2173 void
-
2174 testLedgerEntryDID()
-
2175 {
-
2176 testcase("ledger_entry Request DID");
-
2177 using namespace test::jtx;
-
2178 using namespace std::literals::chrono_literals;
-
2179 Env env{*this};
-
2180 Account const alice{"alice"};
-
2181
-
2182 env.fund(XRP(10000), alice);
-
2183 env.close();
-
2184
-
2185 // Lambda to create a DID.
-
2186 auto didCreate = [](test::jtx::Account const& account) {
-
2187 Json::Value jv;
-
2188 jv[jss::TransactionType] = jss::DIDSet;
-
2189 jv[jss::Account] = account.human();
-
2190 jv[sfDIDDocument.jsonName] = strHex(std::string{"data"});
-
2191 jv[sfURI.jsonName] = strHex(std::string{"uri"});
-
2192 return jv;
-
2193 };
-
2194
-
2195 env(didCreate(alice));
-
2196 env.close();
-
2197
-
2198 std::string const ledgerHash{to_string(env.closed()->info().hash)};
-
2199
-
2200 {
-
2201 // Request the DID using its index.
-
2202 Json::Value jvParams;
-
2203 jvParams[jss::did] = alice.human();
-
2204 jvParams[jss::ledger_hash] = ledgerHash;
-
2205 Json::Value const jrr = env.rpc(
-
2206 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2207 BEAST_EXPECT(
-
2208 jrr[jss::node][sfDIDDocument.jsonName] ==
-
2209 strHex(std::string{"data"}));
-
2210 BEAST_EXPECT(
-
2211 jrr[jss::node][sfURI.jsonName] == strHex(std::string{"uri"}));
-
2212 }
-
2213 {
-
2214 // Request an index that is not a DID.
-
2215 Json::Value jvParams;
-
2216 jvParams[jss::did] = env.master.human();
-
2217 jvParams[jss::ledger_hash] = ledgerHash;
-
2218 Json::Value const jrr = env.rpc(
-
2219 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2220 checkErrorValue(jrr, "entryNotFound", "");
-
2221 }
-
2222 }
-
2223
-
2224 void
-
2225 testLedgerEntryInvalidParams(unsigned int apiVersion)
-
2226 {
-
2227 testcase(
-
2228 "ledger_entry Request With Invalid Parameters v" +
-
2229 std::to_string(apiVersion));
-
2230 using namespace test::jtx;
-
2231 Env env{*this};
-
2232
-
2233 std::string const ledgerHash{to_string(env.closed()->info().hash)};
-
2234
-
2235 auto makeParams = [&apiVersion](std::function<void(Json::Value&)> f) {
-
2236 Json::Value params;
-
2237 params[jss::api_version] = apiVersion;
-
2238 f(params);
-
2239 return params;
-
2240 };
-
2241 // "features" is not an option supported by ledger_entry.
-
2242 {
-
2243 auto const jvParams =
-
2244 makeParams([&ledgerHash](Json::Value& jvParams) {
-
2245 jvParams[jss::features] = ledgerHash;
-
2246 jvParams[jss::ledger_hash] = ledgerHash;
-
2247 });
-
2248 Json::Value const jrr = env.rpc(
-
2249 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2250
-
2251 if (apiVersion < 2u)
-
2252 checkErrorValue(jrr, "unknownOption", "");
-
2253 else
-
2254 checkErrorValue(jrr, "invalidParams", "");
-
2255 }
-
2256 Json::Value const injectObject = []() {
-
2257 Json::Value obj(Json::objectValue);
-
2258 obj[jss::account] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
-
2259 obj[jss::ledger_index] = "validated";
-
2260 return obj;
-
2261 }();
-
2262 Json::Value const injectArray = []() {
-
2263 Json::Value arr(Json::arrayValue);
-
2264 arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
-
2265 arr[1u] = "validated";
-
2266 return arr;
-
2267 }();
-
2268
-
2269 // invalid input for fields that can handle an object, but can't handle
-
2270 // an array
-
2271 for (auto const& field :
-
2272 {jss::directory, jss::escrow, jss::offer, jss::ticket, jss::amm})
-
2273 {
-
2274 auto const jvParams =
-
2275 makeParams([&field, &injectArray](Json::Value& jvParams) {
-
2276 jvParams[field] = injectArray;
-
2277 });
-
2278
-
2279 Json::Value const jrr = env.rpc(
-
2280 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2281
-
2282 if (apiVersion < 2u)
-
2283 checkErrorValue(jrr, "internal", "Internal error.");
-
2284 else
-
2285 checkErrorValue(jrr, "invalidParams", "");
-
2286 }
-
2287 // Fields that can handle objects just fine
-
2288 for (auto const& field :
-
2289 {jss::directory, jss::escrow, jss::offer, jss::ticket, jss::amm})
-
2290 {
-
2291 auto const jvParams =
-
2292 makeParams([&field, &injectObject](Json::Value& jvParams) {
-
2293 jvParams[field] = injectObject;
-
2294 });
-
2295
-
2296 Json::Value const jrr = env.rpc(
-
2297 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2298
-
2299 checkErrorValue(jrr, "malformedRequest", "");
-
2300 }
-
2301
-
2302 for (auto const& inject : {injectObject, injectArray})
-
2303 {
-
2304 // invalid input for fields that can't handle an object or an array
-
2305 for (auto const& field :
-
2306 {jss::index,
-
2307 jss::account_root,
-
2308 jss::check,
-
2309 jss::payment_channel})
-
2310 {
-
2311 auto const jvParams =
-
2312 makeParams([&field, &inject](Json::Value& jvParams) {
-
2313 jvParams[field] = inject;
-
2314 });
-
2315
-
2316 Json::Value const jrr = env.rpc(
-
2317 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2318
-
2319 if (apiVersion < 2u)
-
2320 checkErrorValue(jrr, "internal", "Internal error.");
-
2321 else
-
2322 checkErrorValue(jrr, "invalidParams", "");
-
2323 }
-
2324 // directory sub-fields
-
2325 for (auto const& field : {jss::dir_root, jss::owner})
-
2326 {
-
2327 auto const jvParams =
-
2328 makeParams([&field, &inject](Json::Value& jvParams) {
-
2329 jvParams[jss::directory][field] = inject;
-
2330 });
-
2331
-
2332 Json::Value const jrr = env.rpc(
-
2333 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2334
-
2335 if (apiVersion < 2u)
-
2336 checkErrorValue(jrr, "internal", "Internal error.");
-
2337 else
-
2338 checkErrorValue(jrr, "invalidParams", "");
-
2339 }
-
2340 // escrow sub-fields
-
2341 {
-
2342 auto const jvParams =
-
2343 makeParams([&inject](Json::Value& jvParams) {
-
2344 jvParams[jss::escrow][jss::owner] = inject;
-
2345 jvParams[jss::escrow][jss::seq] = 99;
-
2346 });
-
2347
-
2348 Json::Value const jrr = env.rpc(
-
2349 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2350
-
2351 if (apiVersion < 2u)
-
2352 checkErrorValue(jrr, "internal", "Internal error.");
-
2353 else
-
2354 checkErrorValue(jrr, "invalidParams", "");
-
2355 }
-
2356 // offer sub-fields
-
2357 {
-
2358 auto const jvParams =
-
2359 makeParams([&inject](Json::Value& jvParams) {
-
2360 jvParams[jss::offer][jss::account] = inject;
-
2361 jvParams[jss::offer][jss::seq] = 99;
-
2362 });
-
2363
-
2364 Json::Value const jrr = env.rpc(
-
2365 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2366
-
2367 if (apiVersion < 2u)
-
2368 checkErrorValue(jrr, "internal", "Internal error.");
-
2369 else
-
2370 checkErrorValue(jrr, "invalidParams", "");
-
2371 }
-
2372 // ripple_state sub-fields
-
2373 {
-
2374 auto const jvParams =
-
2375 makeParams([&inject](Json::Value& jvParams) {
-
2376 Json::Value rs(Json::objectValue);
-
2377 rs[jss::currency] = "FOO";
-
2378 rs[jss::accounts] = Json::Value(Json::arrayValue);
-
2379 rs[jss::accounts][0u] =
-
2380 "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
-
2381 rs[jss::accounts][1u] =
-
2382 "rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv";
-
2383 rs[jss::currency] = inject;
-
2384 jvParams[jss::ripple_state] = std::move(rs);
-
2385 });
-
2386
-
2387 Json::Value const jrr = env.rpc(
-
2388 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2389
-
2390 if (apiVersion < 2u)
-
2391 checkErrorValue(jrr, "internal", "Internal error.");
-
2392 else
-
2393 checkErrorValue(jrr, "invalidParams", "");
-
2394 }
-
2395 // ticket sub-fields
-
2396 {
-
2397 auto const jvParams =
-
2398 makeParams([&inject](Json::Value& jvParams) {
-
2399 jvParams[jss::ticket][jss::account] = inject;
-
2400 jvParams[jss::ticket][jss::ticket_seq] = 99;
-
2401 });
-
2402
-
2403 Json::Value const jrr = env.rpc(
-
2404 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2405
-
2406 if (apiVersion < 2u)
-
2407 checkErrorValue(jrr, "internal", "Internal error.");
-
2408 else
-
2409 checkErrorValue(jrr, "invalidParams", "");
-
2410 }
-
2411
-
2412 // Fields that can handle malformed inputs just fine
-
2413 for (auto const& field : {jss::nft_page, jss::deposit_preauth})
-
2414 {
-
2415 auto const jvParams =
-
2416 makeParams([&field, &inject](Json::Value& jvParams) {
-
2417 jvParams[field] = inject;
-
2418 });
-
2419
-
2420 Json::Value const jrr = env.rpc(
-
2421 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2422
-
2423 checkErrorValue(jrr, "malformedRequest", "");
-
2424 }
-
2425 // Subfields of deposit_preauth that can handle malformed inputs
-
2426 // fine
-
2427 for (auto const& field : {jss::owner, jss::authorized})
-
2428 {
-
2429 auto const jvParams =
-
2430 makeParams([&field, &inject](Json::Value& jvParams) {
-
2431 auto pa = Json::Value(Json::objectValue);
-
2432 pa[jss::owner] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
-
2433 pa[jss::authorized] =
-
2434 "rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv";
-
2435 pa[field] = inject;
-
2436 jvParams[jss::deposit_preauth] = std::move(pa);
-
2437 });
-
2438
-
2439 Json::Value const jrr = env.rpc(
-
2440 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
2441
-
2442 checkErrorValue(jrr, "malformedRequest", "");
-
2443 }
-
2444 }
-
2445 }
-
2446
-
2451 void
-
2452 testLookupLedger()
-
2453 {
-
2454 testcase("Lookup ledger");
-
2455 using namespace test::jtx;
-
2456
-
2457 auto cfg = envconfig();
-
2458 cfg->FEES.reference_fee = 10;
-
2459 Env env{
-
2460 *this, std::move(cfg), FeatureBitset{}}; // hashes requested below
-
2461 // assume no amendments
-
2462 env.fund(XRP(10000), "alice");
-
2463 env.close();
-
2464 env.fund(XRP(10000), "bob");
-
2465 env.close();
-
2466 env.fund(XRP(10000), "jim");
-
2467 env.close();
-
2468 env.fund(XRP(10000), "jill");
-
2469
-
2470 {
-
2471 // access via the legacy ledger field, keyword index values
-
2472 Json::Value jvParams;
-
2473 jvParams[jss::ledger] = "closed";
-
2474 auto jrr = env.rpc(
-
2475 "json",
-
2476 "ledger",
-
2477 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2478 BEAST_EXPECT(jrr.isMember(jss::ledger));
-
2479 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
-
2480 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
-
2481
-
2482 jvParams[jss::ledger] = "validated";
-
2483 jrr = env.rpc(
-
2484 "json",
-
2485 "ledger",
-
2486 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2487 BEAST_EXPECT(jrr.isMember(jss::ledger));
-
2488 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
-
2489 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
-
2490
-
2491 jvParams[jss::ledger] = "current";
-
2492 jrr = env.rpc(
-
2493 "json",
-
2494 "ledger",
-
2495 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2496 BEAST_EXPECT(jrr.isMember(jss::ledger));
-
2497 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "6");
-
2498
-
2499 // ask for a bad ledger keyword
-
2500 jvParams[jss::ledger] = "invalid";
-
2501 jrr = env.rpc(
-
2502 "json",
-
2503 "ledger",
-
2504 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2505 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
-
2506 BEAST_EXPECT(jrr[jss::error_message] == "ledgerIndexMalformed");
-
2507
-
2508 // numeric index
-
2509 jvParams[jss::ledger] = 4;
-
2510 jrr = env.rpc(
-
2511 "json",
-
2512 "ledger",
-
2513 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2514 BEAST_EXPECT(jrr.isMember(jss::ledger));
-
2515 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
-
2516 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "4");
-
2517
-
2518 // numeric index - out of range
-
2519 jvParams[jss::ledger] = 20;
-
2520 jrr = env.rpc(
-
2521 "json",
-
2522 "ledger",
-
2523 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2524 BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
-
2525 BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
-
2526 }
-
2527
-
2528 {
-
2529 std::string const hash3{
-
2530 "E86DE7F3D7A4D9CE17EF7C8BA08A8F4D"
-
2531 "8F643B9552F0D895A31CDA78F541DE4E"};
-
2532 // access via the ledger_hash field
-
2533 Json::Value jvParams;
-
2534 jvParams[jss::ledger_hash] = hash3;
-
2535 auto jrr = env.rpc(
-
2536 "json",
-
2537 "ledger",
-
2538 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2539 BEAST_EXPECT(jrr.isMember(jss::ledger));
-
2540 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
-
2541 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "3");
-
2542
-
2543 // extra leading hex chars in hash are not allowed
-
2544 jvParams[jss::ledger_hash] = "DEADBEEF" + hash3;
-
2545 jrr = env.rpc(
-
2546 "json",
-
2547 "ledger",
-
2548 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2549 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
-
2550 BEAST_EXPECT(jrr[jss::error_message] == "ledgerHashMalformed");
-
2551
-
2552 // request with non-string ledger_hash
-
2553 jvParams[jss::ledger_hash] = 2;
-
2554 jrr = env.rpc(
-
2555 "json",
-
2556 "ledger",
-
2557 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2558 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
-
2559 BEAST_EXPECT(jrr[jss::error_message] == "ledgerHashNotString");
-
2560
-
2561 // malformed (non hex) hash
-
2562 jvParams[jss::ledger_hash] =
-
2563 "2E81FC6EC0DD943197EGC7E3FBE9AE30"
-
2564 "7F2775F2F7485BB37307984C3C0F2340";
-
2565 jrr = env.rpc(
-
2566 "json",
-
2567 "ledger",
-
2568 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2569 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
-
2570 BEAST_EXPECT(jrr[jss::error_message] == "ledgerHashMalformed");
-
2571
-
2572 // properly formed, but just doesn't exist
-
2573 jvParams[jss::ledger_hash] =
-
2574 "8C3EEDB3124D92E49E75D81A8826A2E6"
-
2575 "5A75FD71FC3FD6F36FEB803C5F1D812D";
-
2576 jrr = env.rpc(
-
2577 "json",
-
2578 "ledger",
-
2579 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2580 BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
-
2581 BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
-
2582 }
-
2583
-
2584 {
-
2585 // access via the ledger_index field, keyword index values
-
2586 Json::Value jvParams;
-
2587 jvParams[jss::ledger_index] = "closed";
-
2588 auto jrr = env.rpc(
-
2589 "json",
-
2590 "ledger",
-
2591 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2592 BEAST_EXPECT(jrr.isMember(jss::ledger));
-
2593 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
-
2594 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
-
2595 BEAST_EXPECT(jrr.isMember(jss::ledger_index));
-
2596
-
2597 jvParams[jss::ledger_index] = "validated";
-
2598 jrr = env.rpc(
-
2599 "json",
-
2600 "ledger",
-
2601 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2602 BEAST_EXPECT(jrr.isMember(jss::ledger));
-
2603 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
-
2604 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
-
2605
-
2606 jvParams[jss::ledger_index] = "current";
-
2607 jrr = env.rpc(
-
2608 "json",
-
2609 "ledger",
-
2610 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2611 BEAST_EXPECT(jrr.isMember(jss::ledger));
-
2612 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "6");
-
2613 BEAST_EXPECT(jrr.isMember(jss::ledger_current_index));
-
2614
-
2615 // ask for a bad ledger keyword
-
2616 jvParams[jss::ledger_index] = "invalid";
-
2617 jrr = env.rpc(
-
2618 "json",
-
2619 "ledger",
-
2620 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2621 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
-
2622 BEAST_EXPECT(jrr[jss::error_message] == "ledgerIndexMalformed");
-
2623
-
2624 // numeric index
-
2625 for (auto i : {1, 2, 3, 4, 5, 6})
-
2626 {
-
2627 jvParams[jss::ledger_index] = i;
-
2628 jrr = env.rpc(
-
2629 "json",
-
2630 "ledger",
-
2631 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2632 BEAST_EXPECT(jrr.isMember(jss::ledger));
-
2633 if (i < 6)
-
2634 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
-
2635 BEAST_EXPECT(
-
2636 jrr[jss::ledger][jss::ledger_index] == std::to_string(i));
-
2637 }
-
2638
-
2639 // numeric index - out of range
-
2640 jvParams[jss::ledger_index] = 7;
-
2641 jrr = env.rpc(
-
2642 "json",
-
2643 "ledger",
-
2644 boost::lexical_cast<std::string>(jvParams))[jss::result];
-
2645 BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
-
2646 BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
-
2647 }
-
2648 }
-
2649
-
2650 void
-
2651 testNoQueue()
-
2652 {
-
2653 testcase("Ledger with queueing disabled");
-
2654 using namespace test::jtx;
-
2655 Env env{*this};
-
2656
-
2657 Json::Value jv;
-
2658 jv[jss::ledger_index] = "current";
-
2659 jv[jss::queue] = true;
-
2660 jv[jss::expand] = true;
-
2661
-
2662 auto jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
-
2663 BEAST_EXPECT(!jrr.isMember(jss::queue_data));
-
2664 }
-
2665
-
2666 void
-
2667 testQueue()
-
2668 {
-
2669 testcase("Ledger with Queued Transactions");
-
2670 using namespace test::jtx;
-
2671 auto cfg = envconfig([](std::unique_ptr<Config> cfg) {
-
2672 auto& section = cfg->section("transaction_queue");
-
2673 section.set("minimum_txn_in_ledger_standalone", "3");
-
2674 section.set("normal_consensus_increase_percent", "0");
-
2675 return cfg;
-
2676 });
-
2677
-
2678 cfg->FEES.reference_fee = 10;
-
2679 Env env(*this, std::move(cfg));
-
2680
-
2681 Json::Value jv;
-
2682 jv[jss::ledger_index] = "current";
-
2683 jv[jss::queue] = true;
-
2684 jv[jss::expand] = true;
-
2685
-
2686 Account const alice{"alice"};
-
2687 Account const bob{"bob"};
-
2688 Account const charlie{"charlie"};
-
2689 Account const daria{"daria"};
-
2690 env.fund(XRP(10000), alice);
-
2691 env.fund(XRP(10000), bob);
-
2692 env.close();
-
2693 env.fund(XRP(10000), charlie);
-
2694 env.fund(XRP(10000), daria);
-
2695 env.close();
-
2696
-
2697 auto jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
-
2698 BEAST_EXPECT(!jrr.isMember(jss::queue_data));
-
2699
-
2700 // Fill the open ledger
-
2701 for (;;)
-
2702 {
-
2703 auto metrics = env.app().getTxQ().getMetrics(*env.current());
-
2704 if (metrics.openLedgerFeeLevel > metrics.minProcessingFeeLevel)
-
2705 break;
-
2706 env(noop(alice));
-
2707 }
-
2708
-
2709 BEAST_EXPECT(env.current()->info().seq == 5);
-
2710 // Put some txs in the queue
-
2711 // Alice
-
2712 auto aliceSeq = env.seq(alice);
-
2713 env(pay(alice, "george", XRP(1000)),
-
2714 json(R"({"LastLedgerSequence":7})"),
-
2715 ter(terQUEUED));
-
2716 env(offer(alice, XRP(50000), alice["USD"](5000)),
-
2717 seq(aliceSeq + 1),
-
2718 ter(terQUEUED));
-
2719 env(noop(alice), seq(aliceSeq + 2), ter(terQUEUED));
-
2720 // Bob
-
2721 auto batch = [&env](Account a) {
-
2722 auto aSeq = env.seq(a);
-
2723 // Enough fee to get in front of alice in the queue
-
2724 for (int i = 0; i < 10; ++i)
-
2725 {
-
2726 env(noop(a), fee(1000 + i), seq(aSeq + i), ter(terQUEUED));
-
2727 }
-
2728 };
-
2729 batch(bob);
-
2730 // Charlie
-
2731 batch(charlie);
-
2732 // Daria
-
2733 batch(daria);
-
2734
-
2735 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
-
2736 BEAST_EXPECT(jrr[jss::queue_data].size() == 33);
-
2737
-
2738 // Close enough ledgers so that alice's first tx expires.
-
2739 env.close();
-
2740 env.close();
-
2741 env.close();
-
2742 BEAST_EXPECT(env.current()->info().seq == 8);
-
2743
-
2744 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
-
2745 BEAST_EXPECT(jrr[jss::queue_data].size() == 11);
-
2746
-
2747 env.close();
-
2748
-
2749 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
-
2750 const std::string txid0 = [&]() {
-
2751 auto const& parentHash = env.current()->info().parentHash;
-
2752 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
-
2753 {
-
2754 const std::string txid1 = [&]() {
-
2755 auto const& txj = jrr[jss::queue_data][1u];
-
2756 BEAST_EXPECT(txj[jss::account] == alice.human());
-
2757 BEAST_EXPECT(txj[jss::fee_level] == "256");
-
2758 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
-
2759 BEAST_EXPECT(txj["retries_remaining"] == 10);
-
2760 BEAST_EXPECT(txj.isMember(jss::tx));
-
2761 auto const& tx = txj[jss::tx];
-
2762 BEAST_EXPECT(tx[jss::Account] == alice.human());
-
2763 BEAST_EXPECT(tx[jss::TransactionType] == jss::AccountSet);
-
2764 return tx[jss::hash].asString();
-
2765 }();
-
2766
-
2767 auto const& txj = jrr[jss::queue_data][0u];
-
2768 BEAST_EXPECT(txj[jss::account] == alice.human());
-
2769 BEAST_EXPECT(txj[jss::fee_level] == "256");
-
2770 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
-
2771 BEAST_EXPECT(txj["retries_remaining"] == 10);
-
2772 BEAST_EXPECT(txj.isMember(jss::tx));
-
2773 auto const& tx = txj[jss::tx];
-
2774 BEAST_EXPECT(tx[jss::Account] == alice.human());
-
2775 BEAST_EXPECT(tx[jss::TransactionType] == jss::OfferCreate);
-
2776 const auto txid0 = tx[jss::hash].asString();
-
2777 uint256 tx0, tx1;
-
2778 BEAST_EXPECT(tx0.parseHex(txid0));
-
2779 BEAST_EXPECT(tx1.parseHex(txid1));
-
2780 BEAST_EXPECT((tx0 ^ parentHash) < (tx1 ^ parentHash));
-
2781 return txid0;
-
2782 }
-
2783 return std::string{};
-
2784 }();
-
2785
-
2786 env.close();
-
2787
-
2788 jv[jss::expand] = false;
-
2789
-
2790 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
-
2791 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
-
2792 {
-
2793 auto const& parentHash = env.current()->info().parentHash;
-
2794 auto const txid1 = [&]() {
-
2795 auto const& txj = jrr[jss::queue_data][1u];
-
2796 BEAST_EXPECT(txj[jss::account] == alice.human());
-
2797 BEAST_EXPECT(txj[jss::fee_level] == "256");
-
2798 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
-
2799 BEAST_EXPECT(txj.isMember(jss::tx));
-
2800 return txj[jss::tx].asString();
-
2801 }();
-
2802 auto const& txj = jrr[jss::queue_data][0u];
-
2803 BEAST_EXPECT(txj[jss::account] == alice.human());
-
2804 BEAST_EXPECT(txj[jss::fee_level] == "256");
-
2805 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
-
2806 BEAST_EXPECT(txj["retries_remaining"] == 9);
-
2807 BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
-
2808 BEAST_EXPECT(txj.isMember(jss::tx));
-
2809 BEAST_EXPECT(txj[jss::tx] == txid0);
-
2810 uint256 tx0, tx1;
-
2811 BEAST_EXPECT(tx0.parseHex(txid0));
-
2812 BEAST_EXPECT(tx1.parseHex(txid1));
-
2813 BEAST_EXPECT((tx0 ^ parentHash) < (tx1 ^ parentHash));
-
2814 }
-
2815
-
2816 env.close();
-
2817
-
2818 jv[jss::expand] = true;
-
2819 jv[jss::binary] = true;
-
2820
-
2821 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
-
2822 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
-
2823 {
-
2824 auto const& txj = jrr[jss::queue_data][1u];
-
2825 BEAST_EXPECT(txj[jss::account] == alice.human());
-
2826 BEAST_EXPECT(txj[jss::fee_level] == "256");
-
2827 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
-
2828 BEAST_EXPECT(txj["retries_remaining"] == 8);
-
2829 BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
-
2830 BEAST_EXPECT(txj.isMember(jss::tx));
-
2831 BEAST_EXPECT(txj[jss::tx].isMember(jss::tx_blob));
-
2832
-
2833 auto const& txj2 = jrr[jss::queue_data][0u];
-
2834 BEAST_EXPECT(txj2[jss::account] == alice.human());
-
2835 BEAST_EXPECT(txj2[jss::fee_level] == "256");
-
2836 BEAST_EXPECT(txj2["preflight_result"] == "tesSUCCESS");
-
2837 BEAST_EXPECT(txj2["retries_remaining"] == 10);
-
2838 BEAST_EXPECT(!txj2.isMember("last_result"));
-
2839 BEAST_EXPECT(txj2.isMember(jss::tx));
-
2840 BEAST_EXPECT(txj2[jss::tx].isMember(jss::tx_blob));
-
2841 }
-
2842
-
2843 for (int i = 0; i != 9; ++i)
-
2844 {
-
2845 env.close();
-
2846 }
-
2847
-
2848 jv[jss::expand] = false;
-
2849 jv[jss::binary] = false;
-
2850
-
2851 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
-
2852 const std::string txid2 = [&]() {
-
2853 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 1))
-
2854 {
-
2855 auto const& txj = jrr[jss::queue_data][0u];
-
2856 BEAST_EXPECT(txj[jss::account] == alice.human());
-
2857 BEAST_EXPECT(txj[jss::fee_level] == "256");
-
2858 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
-
2859 BEAST_EXPECT(txj["retries_remaining"] == 1);
-
2860 BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
-
2861 BEAST_EXPECT(txj.isMember(jss::tx));
-
2862 BEAST_EXPECT(txj[jss::tx] != txid0);
-
2863 return txj[jss::tx].asString();
-
2864 }
-
2865 return std::string{};
-
2866 }();
-
2867
-
2868 jv[jss::full] = true;
-
2869
-
2870 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
-
2871 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 1))
-
2872 {
-
2873 auto const& txj = jrr[jss::queue_data][0u];
-
2874 BEAST_EXPECT(txj[jss::account] == alice.human());
-
2875 BEAST_EXPECT(txj[jss::fee_level] == "256");
-
2876 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
-
2877 BEAST_EXPECT(txj["retries_remaining"] == 1);
-
2878 BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
-
2879 BEAST_EXPECT(txj.isMember(jss::tx));
-
2880 auto const& tx = txj[jss::tx];
-
2881 BEAST_EXPECT(tx[jss::Account] == alice.human());
-
2882 BEAST_EXPECT(tx[jss::TransactionType] == jss::AccountSet);
-
2883 BEAST_EXPECT(tx[jss::hash] == txid2);
-
2884 }
-
2885 }
-
2886
-
2887 void
-
2888 testLedgerAccountsOption()
-
2889 {
-
2890 testcase("Ledger Request, Accounts Hashes");
-
2891 using namespace test::jtx;
-
2892
-
2893 Env env{*this};
-
2894
-
2895 env.close();
-
2896
-
2897 std::string index;
-
2898 {
-
2899 Json::Value jvParams;
-
2900 jvParams[jss::ledger_index] = 3u;
-
2901 jvParams[jss::accounts] = true;
-
2902 jvParams[jss::expand] = true;
-
2903 jvParams[jss::type] = "hashes";
-
2904 auto const jrr =
-
2905 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
-
2906 BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
-
2907 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
-
2908 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 1u);
-
2909 BEAST_EXPECT(
-
2910 jrr[jss::ledger][jss::accountState][0u]["LedgerEntryType"] ==
-
2911 jss::LedgerHashes);
-
2912 index = jrr[jss::ledger][jss::accountState][0u]["index"].asString();
-
2913 }
-
2914 {
-
2915 Json::Value jvParams;
-
2916 jvParams[jss::ledger_index] = 3u;
-
2917 jvParams[jss::accounts] = true;
-
2918 jvParams[jss::expand] = false;
-
2919 jvParams[jss::type] = "hashes";
-
2920 auto const jrr =
-
2921 env.rpc("json", "ledger", to_string(jvParams))[jss::result];
-
2922 BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
-
2923 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
-
2924 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 1u);
-
2925 BEAST_EXPECT(jrr[jss::ledger][jss::accountState][0u] == index);
-
2926 }
-
2927 }
-
2928
-
2929 void
-
2930 testInvalidOracleLedgerEntry()
-
2931 {
-
2932 testcase("Invalid Oracle Ledger Entry");
-
2933 using namespace ripple::test::jtx;
-
2934 using namespace ripple::test::jtx::oracle;
-
2935
-
2936 Env env(*this);
-
2937 Account const owner("owner");
-
2938 env.fund(XRP(1'000), owner);
-
2939 Oracle oracle(
-
2940 env,
-
2941 {.owner = owner,
-
2942 .fee = static_cast<int>(env.current()->fees().base.drops())});
-
2943
-
2944 // Malformed document id
-
2945 auto res = Oracle::ledgerEntry(env, owner, NoneTag);
-
2946 BEAST_EXPECT(res[jss::error].asString() == "invalidParams");
-
2947 std::vector<AnyValue> invalid = {-1, 1.2, "", "Invalid"};
-
2948 for (auto const& v : invalid)
-
2949 {
-
2950 auto const res = Oracle::ledgerEntry(env, owner, v);
-
2951 BEAST_EXPECT(res[jss::error].asString() == "malformedDocumentID");
-
2952 }
-
2953 // Missing document id
-
2954 res = Oracle::ledgerEntry(env, owner, std::nullopt);
-
2955 BEAST_EXPECT(res[jss::error].asString() == "malformedRequest");
-
2956
-
2957 // Missing account
-
2958 res = Oracle::ledgerEntry(env, std::nullopt, 1);
-
2959 BEAST_EXPECT(res[jss::error].asString() == "malformedRequest");
-
2960
-
2961 // Malformed account
-
2962 std::string malfAccount = to_string(owner.id());
-
2963 malfAccount.replace(10, 1, 1, '!');
-
2964 res = Oracle::ledgerEntry(env, malfAccount, 1);
-
2965 BEAST_EXPECT(res[jss::error].asString() == "malformedAddress");
-
2966 }
-
2967
-
2968 void
-
2969 testOracleLedgerEntry()
-
2970 {
-
2971 testcase("Oracle Ledger Entry");
-
2972 using namespace ripple::test::jtx;
-
2973 using namespace ripple::test::jtx::oracle;
-
2974
-
2975 Env env(*this);
-
2976 auto const baseFee =
-
2977 static_cast<int>(env.current()->fees().base.drops());
-
2978 std::vector<AccountID> accounts;
-
2979 std::vector<std::uint32_t> oracles;
-
2980 for (int i = 0; i < 10; ++i)
-
2981 {
-
2982 Account const owner(std::string("owner") + std::to_string(i));
-
2983 env.fund(XRP(1'000), owner);
-
2984 // different accounts can have the same asset pair
-
2985 Oracle oracle(
-
2986 env, {.owner = owner, .documentID = i, .fee = baseFee});
-
2987 accounts.push_back(owner.id());
-
2988 oracles.push_back(oracle.documentID());
-
2989 // same account can have different asset pair
-
2990 Oracle oracle1(
-
2991 env, {.owner = owner, .documentID = i + 10, .fee = baseFee});
-
2992 accounts.push_back(owner.id());
-
2993 oracles.push_back(oracle1.documentID());
-
2994 }
-
2995 for (int i = 0; i < accounts.size(); ++i)
-
2996 {
-
2997 auto const jv = [&]() {
-
2998 // document id is uint32
-
2999 if (i % 2)
-
3000 return Oracle::ledgerEntry(env, accounts[i], oracles[i]);
-
3001 // document id is string
-
3002 return Oracle::ledgerEntry(
-
3003 env, accounts[i], std::to_string(oracles[i]));
-
3004 }();
-
3005 try
-
3006 {
-
3007 BEAST_EXPECT(
-
3008 jv[jss::node][jss::Owner] == to_string(accounts[i]));
-
3009 }
-
3010 catch (...)
-
3011 {
-
3012 fail();
-
3013 }
-
3014 }
-
3015 }
-
3016
-
3017 void
-
3018 testLedgerEntryMPT()
-
3019 {
-
3020 testcase("ledger_entry Request MPT");
-
3021 using namespace test::jtx;
-
3022 using namespace std::literals::chrono_literals;
-
3023 Env env{*this};
-
3024 Account const alice{"alice"};
-
3025 Account const bob("bob");
-
3026
-
3027 MPTTester mptAlice(env, alice, {.holders = {bob}});
-
3028 mptAlice.create(
-
3029 {.transferFee = 10,
-
3030 .metadata = "123",
-
3031 .ownerCount = 1,
-
3032 .flags = tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow |
-
3033 tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback});
-
3034 mptAlice.authorize({.account = bob, .holderCount = 1});
-
3035
-
3036 std::string const ledgerHash{to_string(env.closed()->info().hash)};
-
3037
-
3038 std::string const badMptID =
-
3039 "00000193B9DDCAF401B5B3B26875986043F82CD0D13B4315";
-
3040 {
-
3041 // Request the MPTIssuance using its MPTIssuanceID.
-
3042 Json::Value jvParams;
-
3043 jvParams[jss::mpt_issuance] = strHex(mptAlice.issuanceID());
-
3044 jvParams[jss::ledger_hash] = ledgerHash;
-
3045 Json::Value const jrr = env.rpc(
-
3046 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
3047 BEAST_EXPECT(
-
3048 jrr[jss::node][sfMPTokenMetadata.jsonName] ==
-
3049 strHex(std::string{"123"}));
-
3050 BEAST_EXPECT(
-
3051 jrr[jss::node][jss::mpt_issuance_id] ==
-
3052 strHex(mptAlice.issuanceID()));
-
3053 }
-
3054 {
-
3055 // Request an index that is not a MPTIssuance.
-
3056 Json::Value jvParams;
-
3057 jvParams[jss::mpt_issuance] = badMptID;
-
3058 jvParams[jss::ledger_hash] = ledgerHash;
-
3059 Json::Value const jrr = env.rpc(
-
3060 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
3061 checkErrorValue(jrr, "entryNotFound", "");
-
3062 }
-
3063 {
-
3064 // Request the MPToken using its owner + mptIssuanceID.
-
3065 Json::Value jvParams;
-
3066 jvParams[jss::mptoken] = Json::objectValue;
-
3067 jvParams[jss::mptoken][jss::account] = bob.human();
-
3068 jvParams[jss::mptoken][jss::mpt_issuance_id] =
-
3069 strHex(mptAlice.issuanceID());
-
3070 jvParams[jss::ledger_hash] = ledgerHash;
-
3071 Json::Value const jrr = env.rpc(
-
3072 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
3073 BEAST_EXPECT(
-
3074 jrr[jss::node][sfMPTokenIssuanceID.jsonName] ==
-
3075 strHex(mptAlice.issuanceID()));
-
3076 }
-
3077 {
-
3078 // Request the MPToken using a bad mptIssuanceID.
-
3079 Json::Value jvParams;
-
3080 jvParams[jss::mptoken] = Json::objectValue;
-
3081 jvParams[jss::mptoken][jss::account] = bob.human();
-
3082 jvParams[jss::mptoken][jss::mpt_issuance_id] = badMptID;
-
3083 jvParams[jss::ledger_hash] = ledgerHash;
-
3084 Json::Value const jrr = env.rpc(
-
3085 "json", "ledger_entry", to_string(jvParams))[jss::result];
-
3086 checkErrorValue(jrr, "entryNotFound", "");
-
3087 }
-
3088 }
-
3089
-
3090 void
-
3091 testLedgerEntryCLI()
-
3092 {
-
3093 testcase("ledger_entry command-line");
-
3094 using namespace test::jtx;
-
3095
-
3096 Env env{*this};
-
3097 Account const alice{"alice"};
-
3098 env.fund(XRP(10000), alice);
-
3099 env.close();
-
3100
-
3101 auto const checkId = keylet::check(env.master, env.seq(env.master));
-
3102
-
3103 env(check::create(env.master, alice, XRP(100)));
-
3104 env.close();
-
3105
-
3106 std::string const ledgerHash{to_string(env.closed()->info().hash)};
-
3107 {
-
3108 // Request a check.
-
3109 Json::Value const jrr =
-
3110 env.rpc("ledger_entry", to_string(checkId.key))[jss::result];
-
3111 BEAST_EXPECT(
-
3112 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
-
3113 BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
-
3114 }
-
3115 }
-
3116
-
3117 void
-
3118 testLedgerEntryPermissionedDomain()
-
3119 {
-
3120 testcase("ledger_entry PermissionedDomain");
-
3121
-
3122 using namespace test::jtx;
-
3123
-
3124 Env env(*this, supported_amendments() | featurePermissionedDomains);
-
3125 Account const issuer{"issuer"};
-
3126 Account const alice{"alice"};
-
3127 Account const bob{"bob"};
-
3128
-
3129 env.fund(XRP(5000), issuer, alice, bob);
-
3130 env.close();
-
3131
-
3132 auto const seq = env.seq(alice);
-
3133 env(pdomain::setTx(alice, {{alice, "first credential"}}));
-
3134 env.close();
-
3135 auto const objects = pdomain::getObjects(alice, env);
-
3136 if (!BEAST_EXPECT(objects.size() == 1))
-
3137 return;
-
3138
-
3139 {
-
3140 // Succeed
-
3141 Json::Value params;
-
3142 params[jss::ledger_index] = jss::validated;
-
3143 params[jss::permissioned_domain][jss::account] = alice.human();
-
3144 params[jss::permissioned_domain][jss::seq] = seq;
-
3145 auto jv = env.rpc("json", "ledger_entry", to_string(params));
-
3146 BEAST_EXPECT(
-
3147 jv.isObject() && jv.isMember(jss::result) &&
-
3148 !jv[jss::result].isMember(jss::error) &&
-
3149 jv[jss::result].isMember(jss::node) &&
-
3150 jv[jss::result][jss::node].isMember(
-
3151 sfLedgerEntryType.jsonName) &&
-
3152 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
-
3153 jss::PermissionedDomain);
-
3154
-
3155 std::string const pdIdx = jv[jss::result][jss::index].asString();
-
3156 BEAST_EXPECT(
-
3157 strHex(keylet::permissionedDomain(alice, seq).key) == pdIdx);
-
3158
-
3159 params.clear();
-
3160 params[jss::ledger_index] = jss::validated;
-
3161 params[jss::permissioned_domain] = pdIdx;
-
3162 jv = env.rpc("json", "ledger_entry", to_string(params));
-
3163 BEAST_EXPECT(
-
3164 jv.isObject() && jv.isMember(jss::result) &&
-
3165 !jv[jss::result].isMember(jss::error) &&
-
3166 jv[jss::result].isMember(jss::node) &&
-
3167 jv[jss::result][jss::node].isMember(
-
3168 sfLedgerEntryType.jsonName) &&
-
3169 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
-
3170 jss::PermissionedDomain);
-
3171 }
-
3172
-
3173 {
-
3174 // Fail, invalid permissioned domain index
-
3175 Json::Value params;
-
3176 params[jss::ledger_index] = jss::validated;
-
3177 params[jss::permissioned_domain] =
-
3178 "12F1F1F1F180D67377B2FAB292A31C922470326268D2B9B74CD1E582645B9A"
-
3179 "DE";
-
3180 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
-
3181 checkErrorValue(jrr[jss::result], "entryNotFound", "");
-
3182 }
-
3183
-
3184 {
-
3185 // Fail, invalid permissioned domain index
-
3186 Json::Value params;
-
3187 params[jss::ledger_index] = jss::validated;
-
3188 params[jss::permissioned_domain] = "NotAHexString";
-
3189 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
-
3190 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
3191 }
-
3192
-
3193 {
-
3194 // Fail, permissioned domain is not an object
-
3195 Json::Value params;
-
3196 params[jss::ledger_index] = jss::validated;
-
3197 params[jss::permissioned_domain] = 10;
-
3198 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
-
3199 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
3200 }
-
3201
-
3202 {
-
3203 // Fail, invalid account
-
3204 Json::Value params;
-
3205 params[jss::ledger_index] = jss::validated;
-
3206 params[jss::permissioned_domain][jss::account] = 1;
-
3207 params[jss::permissioned_domain][jss::seq] = seq;
-
3208 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
-
3209 checkErrorValue(jrr[jss::result], "malformedAddress", "");
-
3210 }
-
3211
-
3212 {
-
3213 // Fail, account is an object
-
3214 Json::Value params;
-
3215 params[jss::ledger_index] = jss::validated;
-
3216 params[jss::permissioned_domain][jss::account] =
-
3217 Json::Value{Json::ValueType::objectValue};
-
3218 params[jss::permissioned_domain][jss::seq] = seq;
-
3219 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
-
3220 checkErrorValue(jrr[jss::result], "malformedAddress", "");
-
3221 }
-
3222
-
3223 {
-
3224 // Fail, no account
-
3225 Json::Value params;
-
3226 params[jss::ledger_index] = jss::validated;
-
3227 params[jss::permissioned_domain][jss::account] = "";
-
3228 params[jss::permissioned_domain][jss::seq] = seq;
-
3229 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
-
3230 checkErrorValue(jrr[jss::result], "malformedAddress", "");
-
3231 }
-
3232
-
3233 {
-
3234 // Fail, invalid sequence
-
3235 Json::Value params;
-
3236 params[jss::ledger_index] = jss::validated;
-
3237 params[jss::permissioned_domain][jss::account] = alice.human();
-
3238 params[jss::permissioned_domain][jss::seq] = "12g";
-
3239 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
-
3240 checkErrorValue(jrr[jss::result], "malformedRequest", "");
-
3241 }
-
3242 }
-
3243
-
3244public:
-
3245 void
-
3246 run() override
-
3247 {
-
3248 testLedgerRequest();
-
3249 testBadInput();
-
3250 testLedgerCurrent();
-
3251 testMissingLedgerEntryLedgerHash();
-
3252 testLedgerFull();
-
3253 testLedgerFullNonAdmin();
-
3254 testLedgerAccounts();
-
3255 testLedgerEntryAccountRoot();
-
3256 testLedgerEntryCheck();
-
3257 testLedgerEntryCredentials();
-
3258 testLedgerEntryDepositPreauth();
-
3259 testLedgerEntryDepositPreauthCred();
-
3260 testLedgerEntryDirectory();
-
3261 testLedgerEntryEscrow();
-
3262 testLedgerEntryOffer();
-
3263 testLedgerEntryPayChan();
-
3264 testLedgerEntryRippleState();
-
3265 testLedgerEntryTicket();
-
3266 testLookupLedger();
-
3267 testNoQueue();
-
3268 testQueue();
-
3269 testLedgerAccountsOption();
-
3270 testLedgerEntryDID();
-
3271 testInvalidOracleLedgerEntry();
-
3272 testOracleLedgerEntry();
-
3273 testLedgerEntryMPT();
-
3274 testLedgerEntryCLI();
-
3275 testLedgerEntryPermissionedDomain();
-
3276
-
3277 forAllApiVersions(std::bind_front(
-
3278 &LedgerRPC_test::testLedgerEntryInvalidParams, this));
-
3279 }
-
3280};
-
3281
-
3282BEAST_DEFINE_TESTSUITE(LedgerRPC, app, ripple);
-
3283BEAST_DEFINE_TESTSUITE(LedgerRPC_XChain, app, ripple);
-
3284
-
3285} // namespace ripple
-
T any_of(T... args)
+
761BEAST_DEFINE_TESTSUITE(LedgerRPC, app, ripple);
+
762
+
763} // namespace test
+
764} // namespace ripple
-
-
T bind_front(T... args)
Represents a JSON value.
Definition: json_value.h:148
-
Json::UInt UInt
Definition: json_value.h:155
-
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:712
-
void clear()
Remove all object members and array elements.
Definition: json_value.cpp:759
-
Int asInt() const
Definition: json_value.cpp:509
-
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:897
-
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:475
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:949
A testsuite class.
Definition: suite.h:55
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
+
virtual TxQ & getTxQ()=0
Definition: Feature.h:147
-
-
void testLedgerEntryBridge()
-
void checkErrorValue(Json::Value const &jv, std::string const &err, std::string const &msg)
-
void run() override
Runs the suite.
-
void testLedgerEntryClaimID()
-
void testLedgerEntryCreateAccountClaimID()
-
-
void testLedgerCurrent()
-
void testOracleLedgerEntry()
-
void testLedgerEntryMPT()
-
void testBadInput()
-
void testLookupLedger()
ledger RPC requests as a way to drive input options to lookupLedger.
-
void testLedgerEntryCheck()
-
void testLedgerFullNonAdmin()
-
void testLedgerFull()
-
void testNoQueue()
-
void testInvalidOracleLedgerEntry()
-
void testLedgerEntryDirectory()
-
void testLedgerAccountsOption()
-
void testLedgerEntryDID()
-
void testQueue()
-
void testLedgerEntryEscrow()
-
void testLedgerEntryOffer()
-
void checkErrorValue(Json::Value const &jv, std::string const &err, std::string const &msg)
-
void testLedgerEntryDepositPreauth()
-
void testLedgerEntryCredentials()
-
void testLedgerEntryPayChan()
-
void testMissingLedgerEntryLedgerHash()
-
void testLedgerAccounts()
-
void testLedgerEntryDepositPreauthCred()
-
void run() override
Runs the suite.
-
void testLedgerEntryInvalidParams(unsigned int apiVersion)
-
void testLedgerEntryCLI()
-
void testLedgerRequest()
-
void testLedgerEntryRippleState()
-
void testLedgerEntryAccountRoot()
-
std::string makeBadAddress(std::string good)
-
void testLedgerEntryTicket()
-
void testLedgerEntryPermissionedDomain()
-
A public key.
Definition: PublicKey.h:62
-
Definition: STAmount.h:50
+
Metrics getMetrics(OpenView const &view) const
Returns fee metrics in reference fee level units.
Definition: TxQ.cpp:1778
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition: base_uint.h:503
+
+
void testLedgerCurrent()
+
void testQueue()
+
void testNoQueue()
+
void testLedgerFull()
+
void checkErrorValue(Json::Value const &jv, std::string const &err, std::string const &msg)
+
void run() override
Runs the suite.
+
void testLookupLedger()
ledger RPC requests as a way to drive input options to lookupLedger.
+
void testBadInput()
+
std::string makeBadAddress(std::string good)
+
void testLedgerRequest()
+
void testLedgerFullNonAdmin()
+
void testLedgerAccounts()
+
void testLedgerAccountsOption()
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
A transaction testing environment.
Definition: Env.h:120
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:328
+
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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:767
+
Application & app()
Definition: Env.h:260
+
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:769
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
-
Definition: mpt.h:143
-
void create(MPTCreate const &arg=MPTCreate{})
Definition: mpt.cpp:86
-
Oracle class facilitates unit-testing of the Price Oracle feature.
Definition: Oracle.h:120
-
+
Set the fee on a JTx.
Definition: fee.h:37
+
Inject raw JSON.
Definition: jtx_json.h:33
+
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:35
T empty(T... args)
-
-
@ nullValue
'null' value
Definition: json_value.h:37
-
@ arrayValue
array value (ordered list)
Definition: json_value.h:43
-
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
-
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:201
-
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition: Indexes.cpp:547
-
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:175
-
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition: Indexes.cpp:327
-
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition: Indexes.cpp:386
-
Definition: Oracle_test.cpp:27
-
Definition: Check_test.cpp:27
+
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:202
+
std::unique_ptr< Config > no_admin(std::unique_ptr< Config >)
adjust config so no admin ports are enabled
Definition: envconfig.cpp:76
+
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
+
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
+
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:29
+
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
-
constexpr std::uint32_t asfDepositAuth
Definition: TxFlags.h:84
-
AccountID const & xrpAccount()
Compute AccountID from public key.
Definition: AccountID.cpp:178
-
constexpr std::uint32_t const tfMPTCanTransfer
Definition: TxFlags.h:145
-
constexpr std::uint32_t const tfMPTCanTrade
Definition: TxFlags.h:144
-
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
-
std::size_t constexpr maxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition: Protocol.h:107
-
@ invalid
Timely, but invalid signature.
-
void forAllApiVersions(Fn const &fn, Args &&... args)
Definition: ApiVersion.h:102
-
constexpr std::uint32_t tfUniversal
Definition: TxFlags.h:61
-
uint256 getTicketIndex(AccountID const &account, std::uint32_t uSequence)
Definition: Indexes.cpp:147
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
@ noop
@ terQUEUED
Definition: TER.h:225
-
constexpr std::uint32_t const tfMPTCanEscrow
Definition: TxFlags.h:143
-
constexpr std::uint32_t const tfMPTRequireAuth
Definition: TxFlags.h:142
-
constexpr std::uint32_t const tfMPTCanLock
Definition: TxFlags.h:141
-
constexpr std::uint32_t const tfMPTCanClawback
Definition: TxFlags.h:146
-
T push_back(T... args)
T replace(T... args)
-
T size(T... args)
-
@ none
Definition: STBase.h:44
-
uint256 key
Definition: Keylet.h:40
-
-
Account const scAlice
-
Account const scCarol
-
Account const scAttester
-
std::vector< Account > const payee
-
Account const mcAlice
-
Account const mcDoor
-
Json::Value jvb
-
static constexpr int drop_per_xrp
-
STAmount const reward
-
Account const mcBob
-
FeatureBitset const features
-
std::vector< signer > const signers
-
void createBridgeObjects(Env &mcEnv, Env &scEnv)
-
Account const scBob
-
Json::Value const jvXRPBridgeRPC
Set the sequence number on a JTx.
Definition: seq.h:34
-
T to_string(T... args)
-